Update vendored NetIPC library (#22649)
Costa Tsaousis committed
Jun 8, 2026 at 09:25 UTC
36cf14dabb3348b8522d9390ce65edea419c29a1
174 files changed
+24753
-14029
CMakeLists.txt
+38
-2
@@ -2358,12 +2358,42 @@ set_source_files_properties(JudyLTables.c PROPERTIES COMPILE_OPTIONS "-I${CMAKE_
2358
# build netipc (standalone IPC library, no Netdata deps)
2359
#
2360
2361
+set(NETIPC_PROTOCOL_FILES
2362
+ src/libnetdata/netipc/src/protocol/netipc_protocol.c
2363
+ src/libnetdata/netipc/src/protocol/netipc_protocol_increment.c
2364
+ src/libnetdata/netipc/src/protocol/netipc_protocol_string_reverse.c
2365
+ src/libnetdata/netipc/src/protocol/netipc_protocol_cgroups_snapshot.c
2366
+ src/libnetdata/netipc/src/protocol/netipc_protocol_lookup_common.c
2367
+ src/libnetdata/netipc/src/protocol/netipc_protocol_cgroups_lookup.c
2368
+ src/libnetdata/netipc/src/protocol/netipc_protocol_apps_lookup.c
2369
+)
2370
+
2371
+set(NETIPC_SERVICE_COMMON_FILES
2372
+ src/libnetdata/netipc/src/service/netipc_service_common.c
2373
+ src/libnetdata/netipc/src/service/netipc_service_cgroups_cache_common.c
2374
+ src/libnetdata/netipc/src/service/netipc_service_cgroups_cache.c
2375
+ src/libnetdata/netipc/src/service/netipc_service_cgroups_snapshot.c
2376
+ src/libnetdata/netipc/src/service/netipc_service_cgroups_lookup.c
2377
+ src/libnetdata/netipc/src/service/netipc_service_apps_lookup.c
2378
+)
2379
+
2380
if(OS_LINUX)
2381
set(NETIPC_FILES
2363
- src/libnetdata/netipc/src/protocol/netipc_protocol.c
2382
+ ${NETIPC_PROTOCOL_FILES}
2383
src/libnetdata/netipc/src/transport/posix/netipc_uds.c
2384
+ src/libnetdata/netipc/src/transport/posix/netipc_uds_handshake.c
2385
+ src/libnetdata/netipc/src/transport/posix/netipc_uds_inflight.c
2386
+ src/libnetdata/netipc/src/transport/posix/netipc_uds_lifecycle.c
2387
+ src/libnetdata/netipc/src/transport/posix/netipc_uds_receive.c
2388
+ src/libnetdata/netipc/src/transport/posix/netipc_uds_send.c
2389
src/libnetdata/netipc/src/transport/posix/netipc_shm.c
2390
+ ${NETIPC_SERVICE_COMMON_FILES}
2391
src/libnetdata/netipc/src/service/netipc_service.c
2392
+ src/libnetdata/netipc/src/service/netipc_service_posix_client.c
2393
+ src/libnetdata/netipc/src/service/netipc_service_posix_client_connect.c
2394
+ src/libnetdata/netipc/src/service/netipc_service_posix_client_call.c
2395
+ src/libnetdata/netipc/src/service/netipc_service_posix_server.c
2396
+ src/libnetdata/netipc/src/service/netipc_service_posix_server_session.c
2397
)
2398
2399
add_library(netipc STATIC ${NETIPC_FILES})
@@ -2372,10 +2402,16 @@ if(OS_LINUX)
2402
set_target_properties(netipc PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED ON)
2403
elseif(OS_WINDOWS)
2404
set(NETIPC_FILES
2375
- src/libnetdata/netipc/src/protocol/netipc_protocol.c
2405
+ ${NETIPC_PROTOCOL_FILES}
2406
src/libnetdata/netipc/src/transport/windows/netipc_named_pipe.c
2407
src/libnetdata/netipc/src/transport/windows/netipc_win_shm.c
2408
+ ${NETIPC_SERVICE_COMMON_FILES}
2409
src/libnetdata/netipc/src/service/netipc_service_win.c
2410
+ src/libnetdata/netipc/src/service/netipc_service_win_client.c
2411
+ src/libnetdata/netipc/src/service/netipc_service_win_client_connect.c
2412
+ src/libnetdata/netipc/src/service/netipc_service_win_client_call.c
2413
+ src/libnetdata/netipc/src/service/netipc_service_win_server.c
2414
+ src/libnetdata/netipc/src/service/netipc_service_win_server_session.c
2415
)
2416
2417
add_library(netipc STATIC ${NETIPC_FILES})
src/crates/netipc/src/bin/interop_codec.rs
+309
@@ -167,6 +167,214 @@ fn do_encode(dir: &str) {
167
let total = b.finish();
168
write_file(dir, "cgroups_resp_empty.bin", &buf[..total]);
169
}
170
+
171
+ // 8. CGROUPS_LOOKUP request variants
172
+ {
173
+ let mut buf = [0u8; 8192];
174
+ let total = encode_cgroups_lookup_request(
175
+ &[b"/sys/fs/cgroup/a", b"/system.slice/docker-abc.scope"],
176
+ &mut buf,
177
+ )
178
+ .unwrap();
179
+ write_file(dir, "cgroups_lookup_req.bin", &buf[..total]);
180
+
181
+ let total = encode_cgroups_lookup_request(&[], &mut buf).unwrap();
182
+ write_file(dir, "cgroups_lookup_req_empty.bin", &buf[..total]);
183
+ }
184
+
185
+ // 9. CGROUPS_LOOKUP response variants
186
+ {
187
+ let mut buf = [0u8; 8192];
188
+ let mut b = CgroupsLookupBuilder::new(&mut buf, 1, 100);
189
+ b.add(
190
+ CGROUP_LOOKUP_KNOWN,
191
+ ORCHESTRATOR_K8S,
192
+ b"/kubepods.slice/pod-a",
193
+ b"pod-a",
194
+ &[
195
+ (b"namespace".as_slice(), b"default".as_slice()),
196
+ (b"pod".as_slice(), b"web".as_slice()),
197
+ ],
198
+ )
199
+ .unwrap();
200
+ let total = b.finish().unwrap();
201
+ write_file(
202
+ dir,
203
+ "cgroups_lookup_resp_known_with_labels.bin",
204
+ &buf[..total],
205
+ );
206
+ }
207
+ {
208
+ let mut buf = [0u8; 8192];
209
+ let mut b = CgroupsLookupBuilder::new(&mut buf, 1, 101);
210
+ b.add(
211
+ CGROUP_LOOKUP_KNOWN,
212
+ ORCHESTRATOR_DOCKER,
213
+ b"/docker/abc",
214
+ b"",
215
+ &[],
216
+ )
217
+ .unwrap();
218
+ let total = b.finish().unwrap();
219
+ write_file(
220
+ dir,
221
+ "cgroups_lookup_resp_known_no_labels.bin",
222
+ &buf[..total],
223
+ );
224
+ }
225
+ {
226
+ let mut buf = [0u8; 8192];
227
+ let mut b = CgroupsLookupBuilder::new(&mut buf, 1, 102);
228
+ b.add(
229
+ CGROUP_LOOKUP_UNKNOWN_RETRY_LATER,
230
+ 0,
231
+ b"/missing/retry",
232
+ b"",
233
+ &[],
234
+ )
235
+ .unwrap();
236
+ let total = b.finish().unwrap();
237
+ write_file(dir, "cgroups_lookup_resp_unknown_retry.bin", &buf[..total]);
238
+ }
239
+ {
240
+ let mut buf = [0u8; 8192];
241
+ let mut b = CgroupsLookupBuilder::new(&mut buf, 1, 103);
242
+ b.add(CGROUP_LOOKUP_UNKNOWN_PERMANENT, 0, b"/gone", b"", &[])
243
+ .unwrap();
244
+ let total = b.finish().unwrap();
245
+ write_file(
246
+ dir,
247
+ "cgroups_lookup_resp_unknown_permanent.bin",
248
+ &buf[..total],
249
+ );
250
+ }
251
+ {
252
+ let mut buf = [0u8; 8192];
253
+ let b = CgroupsLookupBuilder::new(&mut buf, 0, 104);
254
+ let total = b.finish().unwrap();
255
+ write_file(dir, "cgroups_lookup_resp_empty.bin", &buf[..total]);
256
+ }
257
+
258
+ // 10. APPS_LOOKUP request variants
259
+ {
260
+ let mut buf = [0u8; 8192];
261
+ let total = encode_apps_lookup_request(&[0, 1234, 4321], &mut buf).unwrap();
262
+ write_file(dir, "apps_lookup_req.bin", &buf[..total]);
263
+
264
+ let total = encode_apps_lookup_request(&[], &mut buf).unwrap();
265
+ write_file(dir, "apps_lookup_req_empty.bin", &buf[..total]);
266
+ }
267
+
268
+ // 11. APPS_LOOKUP response variants
269
+ {
270
+ let mut buf = [0u8; 8192];
271
+ let mut b = AppsLookupBuilder::new(&mut buf, 1, 200);
272
+ b.add(
273
+ PID_LOOKUP_KNOWN,
274
+ APPS_CGROUP_KNOWN,
275
+ ORCHESTRATOR_DOCKER,
276
+ 1234,
277
+ 1,
278
+ 1000,
279
+ 123456,
280
+ b"123456789012345",
281
+ b"/docker/abc",
282
+ b"container-a",
283
+ &[
284
+ (b"image".as_slice(), b"nginx:latest".as_slice()),
285
+ (b"service".as_slice(), b"web".as_slice()),
286
+ ],
287
+ )
288
+ .unwrap();
289
+ let total = b.finish().unwrap();
290
+ write_file(dir, "apps_lookup_resp_known_full.bin", &buf[..total]);
291
+ }
292
+ {
293
+ let mut buf = [0u8; 8192];
294
+ let mut b = AppsLookupBuilder::new(&mut buf, 1, 201);
295
+ b.add(
296
+ PID_LOOKUP_KNOWN,
297
+ APPS_CGROUP_UNKNOWN_RETRY_LATER,
298
+ 0,
299
+ 1235,
300
+ 1,
301
+ 1000,
302
+ 123457,
303
+ b"app",
304
+ b"/pending",
305
+ b"",
306
+ &[],
307
+ )
308
+ .unwrap();
309
+ let total = b.finish().unwrap();
310
+ write_file(dir, "apps_lookup_resp_known_retry.bin", &buf[..total]);
311
+ }
312
+ {
313
+ let mut buf = [0u8; 8192];
314
+ let mut b = AppsLookupBuilder::new(&mut buf, 1, 202);
315
+ b.add(
316
+ PID_LOOKUP_KNOWN,
317
+ APPS_CGROUP_UNKNOWN_PERMANENT,
318
+ 0,
319
+ 1236,
320
+ 1,
321
+ 1000,
322
+ 123458,
323
+ b"app2",
324
+ b"/permanent",
325
+ b"",
326
+ &[],
327
+ )
328
+ .unwrap();
329
+ let total = b.finish().unwrap();
330
+ write_file(dir, "apps_lookup_resp_known_permanent.bin", &buf[..total]);
331
+ }
332
+ {
333
+ let mut buf = [0u8; 8192];
334
+ let mut b = AppsLookupBuilder::new(&mut buf, 1, 203);
335
+ b.add(
336
+ PID_LOOKUP_KNOWN,
337
+ APPS_CGROUP_HOST_ROOT,
338
+ 0,
339
+ 1237,
340
+ 1,
341
+ 0,
342
+ 123459,
343
+ b"sshd",
344
+ b"",
345
+ b"",
346
+ &[],
347
+ )
348
+ .unwrap();
349
+ let total = b.finish().unwrap();
350
+ write_file(dir, "apps_lookup_resp_known_host_root.bin", &buf[..total]);
351
+ }
352
+ {
353
+ let mut buf = [0u8; 8192];
354
+ let mut b = AppsLookupBuilder::new(&mut buf, 1, 204);
355
+ b.add(
356
+ PID_LOOKUP_UNKNOWN,
357
+ APPS_CGROUP_KNOWN,
358
+ 0,
359
+ 0,
360
+ 0,
361
+ NIPC_UID_UNSET,
362
+ 0,
363
+ b"",
364
+ b"",
365
+ b"",
366
+ &[],
367
+ )
368
+ .unwrap();
369
+ let total = b.finish().unwrap();
370
+ write_file(dir, "apps_lookup_resp_unknown_pid.bin", &buf[..total]);
371
+ }
372
+ {
373
+ let mut buf = [0u8; 8192];
374
+ let b = AppsLookupBuilder::new(&mut buf, 0, 205);
375
+ let total = b.finish().unwrap();
376
+ write_file(dir, "apps_lookup_resp_empty.bin", &buf[..total]);
377
+ }
378
}
379
380
fn do_decode(dir: &str) -> bool {
@@ -326,6 +534,107 @@ fn do_decode(dir: &str) -> bool {
534
}
535
}
536
537
+ // 8. CGROUPS_LOOKUP request variants
538
+ {
539
+ let data = read_file(dir, "cgroups_lookup_req.bin");
540
+ let view = CgroupsLookupRequestView::decode(&data);
541
+ c.check(view.is_ok(), "decode cgroups_lookup_req");
542
+ if let Ok(v) = view {
543
+ c.check(v.item_count == 2, "cgroups_lookup_req item_count");
544
+ c.check(
545
+ v.item(0).unwrap().as_bytes() == b"/sys/fs/cgroup/a",
546
+ "cgroups_lookup_req item0",
547
+ );
548
+ }
549
+ }
550
+ {
551
+ let data = read_file(dir, "cgroups_lookup_req_empty.bin");
552
+ let view = CgroupsLookupRequestView::decode(&data);
553
+ c.check(view.is_ok(), "decode cgroups_lookup_req_empty");
554
+ if let Ok(v) = view {
555
+ c.check(v.item_count == 0, "cgroups_lookup_req_empty count");
556
+ }
557
+ }
558
+
559
+ // 9. CGROUPS_LOOKUP response variants
560
+ {
561
+ let data = read_file(dir, "cgroups_lookup_resp_known_with_labels.bin");
562
+ let view = CgroupsLookupResponseView::decode(&data);
563
+ c.check(view.is_ok(), "decode cgroups_lookup known labels");
564
+ if let Ok(v) = view {
565
+ c.check(v.generation == 100, "cgroups_lookup generation");
566
+ let item = v.item(0).unwrap();
567
+ c.check(item.status == CGROUP_LOOKUP_KNOWN, "cgroups_lookup status");
568
+ c.check(
569
+ item.orchestrator == ORCHESTRATOR_K8S,
570
+ "cgroups_lookup orchestrator",
571
+ );
572
+ c.check(item.label_count == 2, "cgroups_lookup label_count");
573
+ c.check(
574
+ item.label(0).unwrap().key.as_bytes() == b"namespace",
575
+ "cgroups_lookup label",
576
+ );
577
+ }
578
+ }
579
+ for file in [
580
+ "cgroups_lookup_resp_known_no_labels.bin",
581
+ "cgroups_lookup_resp_unknown_retry.bin",
582
+ "cgroups_lookup_resp_unknown_permanent.bin",
583
+ "cgroups_lookup_resp_empty.bin",
584
+ ] {
585
+ let data = read_file(dir, file);
586
+ c.check(CgroupsLookupResponseView::decode(&data).is_ok(), file);
587
+ }
588
+
589
+ // 10. APPS_LOOKUP request variants
590
+ {
591
+ let data = read_file(dir, "apps_lookup_req.bin");
592
+ let view = AppsLookupRequestView::decode(&data);
593
+ c.check(view.is_ok(), "decode apps_lookup_req");
594
+ if let Ok(v) = view {
595
+ c.check(v.item_count == 3, "apps_lookup_req item_count");
596
+ c.check(v.item(0).unwrap() == 0, "apps_lookup_req pid0");
597
+ }
598
+ }
599
+ {
600
+ let data = read_file(dir, "apps_lookup_req_empty.bin");
601
+ let view = AppsLookupRequestView::decode(&data);
602
+ c.check(view.is_ok(), "decode apps_lookup_req_empty");
603
+ if let Ok(v) = view {
604
+ c.check(v.item_count == 0, "apps_lookup_req_empty count");
605
+ }
606
+ }
607
+
608
+ // 11. APPS_LOOKUP response variants
609
+ {
610
+ let data = read_file(dir, "apps_lookup_resp_known_full.bin");
611
+ let view = AppsLookupResponseView::decode(&data);
612
+ c.check(view.is_ok(), "decode apps_lookup known full");
613
+ if let Ok(v) = view {
614
+ let item = v.item(0).unwrap();
615
+ c.check(item.pid == 1234, "apps_lookup pid");
616
+ c.check(item.comm.len == 15, "apps_lookup comm boundary");
617
+ c.check(
618
+ item.cgroup_status == APPS_CGROUP_KNOWN,
619
+ "apps_lookup cgroup status",
620
+ );
621
+ c.check(
622
+ item.label(0).unwrap().value.as_bytes() == b"nginx:latest",
623
+ "apps_lookup label",
624
+ );
625
+ }
626
+ }
627
+ for file in [
628
+ "apps_lookup_resp_known_retry.bin",
629
+ "apps_lookup_resp_known_permanent.bin",
630
+ "apps_lookup_resp_known_host_root.bin",
631
+ "apps_lookup_resp_unknown_pid.bin",
632
+ "apps_lookup_resp_empty.bin",
633
+ ] {
634
+ let data = read_file(dir, file);
635
+ c.check(AppsLookupResponseView::decode(&data).is_ok(), file);
636
+ }
637
+
638
c.report("Rust decode")
639
}
640
src/crates/netipc/src/protocol/cgroups_snapshot.rs
renamed
src/crates/netipc/src/protocol/lookup.rs
new
+13
@@ -0,0 +1,13 @@
1
+//! Lookup service-kind codecs.
2
+
3
+mod apps_lookup;
4
+mod cgroups_lookup;
5
+mod common;
6
+
7
+pub use apps_lookup::*;
8
+pub use cgroups_lookup::*;
9
+pub use common::{
10
+ LookupLabelView, LOOKUP_DIR_ENTRY_SIZE, LOOKUP_LABEL_ENTRY_SIZE, ORCHESTRATOR_DOCKER,
11
+ ORCHESTRATOR_K8S, ORCHESTRATOR_KVM, ORCHESTRATOR_LXC, ORCHESTRATOR_NSPAWN, ORCHESTRATOR_PODMAN,
12
+ ORCHESTRATOR_SYSTEMD, ORCHESTRATOR_UNKNOWN,
13
+};
src/crates/netipc/src/protocol/lookup/apps_lookup.rs
new
+892
@@ -0,0 +1,892 @@
1
+//! APPS_LOOKUP codec.
2
+
3
+use super::common::*;
4
+use crate::protocol::{align8, NipcError, StrView};
5
+
6
+pub const NIPC_UID_UNSET: u32 = u32::MAX;
7
+
8
+pub const PID_LOOKUP_KNOWN: u16 = 0;
9
+pub const PID_LOOKUP_UNKNOWN: u16 = 1;
10
+
11
+pub const APPS_CGROUP_KNOWN: u16 = 0;
12
+pub const APPS_CGROUP_UNKNOWN_RETRY_LATER: u16 = 1;
13
+pub const APPS_CGROUP_UNKNOWN_PERMANENT: u16 = 2;
14
+pub const APPS_CGROUP_HOST_ROOT: u16 = 3;
15
+
16
+pub const APPS_LOOKUP_REQ_HDR_SIZE: usize = 16;
17
+pub const APPS_LOOKUP_RESP_HDR_SIZE: usize = 16;
18
+pub const APPS_LOOKUP_ITEM_HDR_SIZE: usize = 60;
19
+pub const APPS_LOOKUP_KEY_SIZE: usize = 8;
20
+
21
+#[derive(Debug)]
22
+pub struct AppsLookupRequestView<'a> {
23
+ pub item_count: u32,
24
+ payload: &'a [u8],
25
+}
26
+
27
+#[derive(Debug)]
28
+pub struct AppsLookupResponseView<'a> {
29
+ pub layout_version: u16,
30
+ pub flags: u16,
31
+ pub item_count: u32,
32
+ pub generation: u64,
33
+ payload: &'a [u8],
34
+}
35
+
36
+#[derive(Debug, Clone, Copy, PartialEq)]
37
+pub struct AppsLookupItemView<'a> {
38
+ pub status: u16,
39
+ pub orchestrator: u16,
40
+ pub cgroup_status: u16,
41
+ pub pid: u32,
42
+ pub ppid: u32,
43
+ pub uid: u32,
44
+ pub starttime: u64,
45
+ pub comm: StrView<'a>,
46
+ pub cgroup_path: StrView<'a>,
47
+ pub cgroup_name: StrView<'a>,
48
+ pub label_count: u16,
49
+ item: &'a [u8],
50
+ label_table_offset: usize,
51
+}
52
+
53
+fn validate_apps_lookup_semantics(
54
+ status: u16,
55
+ cgroup_status: u16,
56
+ orchestrator: u16,
57
+ ppid: u32,
58
+ uid: u32,
59
+ starttime: u64,
60
+ comm_len: u64,
61
+ path_len: u64,
62
+ name_len: u64,
63
+ label_count: u64,
64
+) -> Result<(), NipcError> {
65
+ validate_apps_lookup_domains(status, cgroup_status, comm_len)?;
66
+ if status == PID_LOOKUP_UNKNOWN {
67
+ return validate_apps_lookup_unknown(
68
+ orchestrator,
69
+ cgroup_status,
70
+ ppid,
71
+ uid,
72
+ starttime,
73
+ comm_len,
74
+ path_len,
75
+ name_len,
76
+ label_count,
77
+ );
78
+ }
79
+ validate_apps_lookup_known(
80
+ cgroup_status,
81
+ orchestrator,
82
+ comm_len,
83
+ path_len,
84
+ name_len,
85
+ label_count,
86
+ )
87
+}
88
+
89
+fn validate_apps_lookup_domains(
90
+ status: u16,
91
+ cgroup_status: u16,
92
+ comm_len: u64,
93
+) -> Result<(), NipcError> {
94
+ if status != PID_LOOKUP_KNOWN && status != PID_LOOKUP_UNKNOWN {
95
+ return Err(NipcError::BadLayout);
96
+ }
97
+ if cgroup_status != APPS_CGROUP_KNOWN
98
+ && cgroup_status != APPS_CGROUP_UNKNOWN_RETRY_LATER
99
+ && cgroup_status != APPS_CGROUP_UNKNOWN_PERMANENT
100
+ && cgroup_status != APPS_CGROUP_HOST_ROOT
101
+ {
102
+ return Err(NipcError::BadLayout);
103
+ }
104
+ if comm_len > 15 {
105
+ return Err(NipcError::BadLayout);
106
+ }
107
+ Ok(())
108
+}
109
+
110
+fn validate_apps_lookup_unknown(
111
+ orchestrator: u16,
112
+ cgroup_status: u16,
113
+ ppid: u32,
114
+ uid: u32,
115
+ starttime: u64,
116
+ comm_len: u64,
117
+ path_len: u64,
118
+ name_len: u64,
119
+ label_count: u64,
120
+) -> Result<(), NipcError> {
121
+ if orchestrator != 0
122
+ || cgroup_status != 0
123
+ || ppid != 0
124
+ || uid != NIPC_UID_UNSET
125
+ || starttime != 0
126
+ || comm_len != 0
127
+ || path_len != 0
128
+ || name_len != 0
129
+ || label_count != 0
130
+ {
131
+ return Err(NipcError::BadLayout);
132
+ }
133
+ Ok(())
134
+}
135
+
136
+fn validate_apps_lookup_known(
137
+ cgroup_status: u16,
138
+ orchestrator: u16,
139
+ comm_len: u64,
140
+ path_len: u64,
141
+ name_len: u64,
142
+ label_count: u64,
143
+) -> Result<(), NipcError> {
144
+ if comm_len == 0 {
145
+ return Err(NipcError::BadLayout);
146
+ }
147
+ match cgroup_status {
148
+ APPS_CGROUP_KNOWN => {
149
+ if path_len == 0 {
150
+ return Err(NipcError::BadLayout);
151
+ }
152
+ }
153
+ APPS_CGROUP_UNKNOWN_RETRY_LATER => {
154
+ if orchestrator != 0 || name_len != 0 || label_count != 0 {
155
+ return Err(NipcError::BadLayout);
156
+ }
157
+ }
158
+ APPS_CGROUP_UNKNOWN_PERMANENT => {
159
+ if path_len == 0 || orchestrator != 0 || name_len != 0 || label_count != 0 {
160
+ return Err(NipcError::BadLayout);
161
+ }
162
+ }
163
+ APPS_CGROUP_HOST_ROOT => {
164
+ if orchestrator != 0 || path_len != 0 || name_len != 0 || label_count != 0 {
165
+ return Err(NipcError::BadLayout);
166
+ }
167
+ }
168
+ _ => return Err(NipcError::BadLayout),
169
+ }
170
+ Ok(())
171
+}
172
+
173
+pub fn encode_apps_lookup_request(pids: &[u32], buf: &mut [u8]) -> Result<usize, NipcError> {
174
+ let count = pids.len();
175
+ let dir_size = count
176
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
177
+ .ok_or(NipcError::Overflow)?;
178
+ let key_size = count
179
+ .checked_mul(APPS_LOOKUP_KEY_SIZE)
180
+ .ok_or(NipcError::Overflow)?;
181
+ let packed_start = APPS_LOOKUP_REQ_HDR_SIZE
182
+ .checked_add(dir_size)
183
+ .ok_or(NipcError::Overflow)?;
184
+ let total = packed_start
185
+ .checked_add(key_size)
186
+ .ok_or(NipcError::Overflow)?;
187
+ if total > buf.len() {
188
+ return Err(NipcError::Overflow);
189
+ }
190
+
191
+ for (i, pid) in pids.iter().enumerate() {
192
+ let dir = APPS_LOOKUP_REQ_HDR_SIZE + i * LOOKUP_DIR_ENTRY_SIZE;
193
+ let key_offset = i
194
+ .checked_mul(APPS_LOOKUP_KEY_SIZE)
195
+ .ok_or(NipcError::Overflow)?;
196
+ put_u32(buf, dir, checked_u32(key_offset)?);
197
+ put_u32(buf, dir + 4, APPS_LOOKUP_KEY_SIZE as u32);
198
+ let key = packed_start + i * APPS_LOOKUP_KEY_SIZE;
199
+ put_u32(buf, key, *pid);
200
+ put_u32(buf, key + 4, 0);
201
+ }
202
+
203
+ put_u16(buf, 0, 1);
204
+ put_u16(buf, 2, 0);
205
+ put_u32(buf, 4, checked_u32(count)?);
206
+ put_u32(buf, 8, 0);
207
+ put_u32(buf, 12, 0);
208
+ Ok(total)
209
+}
210
+
211
+impl<'a> AppsLookupRequestView<'a> {
212
+ pub fn decode(buf: &'a [u8]) -> Result<Self, NipcError> {
213
+ if buf.len() < APPS_LOOKUP_REQ_HDR_SIZE {
214
+ return Err(NipcError::Truncated);
215
+ }
216
+ if u16_at(buf, 0) != 1 || u16_at(buf, 2) != 0 || u32_at(buf, 8) != 0 || u32_at(buf, 12) != 0
217
+ {
218
+ return Err(NipcError::BadLayout);
219
+ }
220
+ let item_count = u32_at(buf, 4);
221
+ let dir_size = (item_count as usize)
222
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
223
+ .ok_or(NipcError::BadItemCount)?;
224
+ let dir_end = APPS_LOOKUP_REQ_HDR_SIZE
225
+ .checked_add(dir_size)
226
+ .ok_or(NipcError::BadItemCount)?;
227
+ if dir_end > buf.len() {
228
+ return Err(NipcError::Truncated);
229
+ }
230
+ validate_lookup_dir(
231
+ buf,
232
+ APPS_LOOKUP_REQ_HDR_SIZE,
233
+ item_count,
234
+ buf.len() - dir_end,
235
+ 0,
236
+ Some(APPS_LOOKUP_KEY_SIZE),
237
+ )?;
238
+ for i in 0..item_count as usize {
239
+ let base = APPS_LOOKUP_REQ_HDR_SIZE + i * LOOKUP_DIR_ENTRY_SIZE;
240
+ let off = u32_at(buf, base) as usize;
241
+ let key = checked_subslice(buf, dir_end, off, APPS_LOOKUP_KEY_SIZE)?;
242
+ if u32_at(key, 4) != 0 {
243
+ return Err(NipcError::BadLayout);
244
+ }
245
+ }
246
+ Ok(Self {
247
+ item_count,
248
+ payload: buf,
249
+ })
250
+ }
251
+
252
+ pub fn item(&self, index: u32) -> Result<u32, NipcError> {
253
+ if index >= self.item_count {
254
+ return Err(NipcError::OutOfBounds);
255
+ }
256
+ let dir_size = (self.item_count as usize)
257
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
258
+ .ok_or(NipcError::BadItemCount)?;
259
+ let packed_start = APPS_LOOKUP_REQ_HDR_SIZE
260
+ .checked_add(dir_size)
261
+ .ok_or(NipcError::BadItemCount)?;
262
+ let base = APPS_LOOKUP_REQ_HDR_SIZE
263
+ .checked_add(
264
+ (index as usize)
265
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
266
+ .ok_or(NipcError::BadItemCount)?,
267
+ )
268
+ .ok_or(NipcError::BadItemCount)?;
269
+ let off = u32_at(self.payload, base) as usize;
270
+ Ok(u32_at(
271
+ checked_subslice(self.payload, packed_start, off, APPS_LOOKUP_KEY_SIZE)?,
272
+ 0,
273
+ ))
274
+ }
275
+}
276
+
277
+impl<'a> AppsLookupResponseView<'a> {
278
+ pub fn decode(buf: &'a [u8]) -> Result<Self, NipcError> {
279
+ if buf.len() < APPS_LOOKUP_RESP_HDR_SIZE {
280
+ return Err(NipcError::Truncated);
281
+ }
282
+ if u16_at(buf, 0) != 1 || u16_at(buf, 2) != 0 {
283
+ return Err(NipcError::BadLayout);
284
+ }
285
+ let item_count = u32_at(buf, 4);
286
+ let dir_size = (item_count as usize)
287
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
288
+ .ok_or(NipcError::BadItemCount)?;
289
+ let dir_end = APPS_LOOKUP_RESP_HDR_SIZE
290
+ .checked_add(dir_size)
291
+ .ok_or(NipcError::BadItemCount)?;
292
+ if dir_end > buf.len() {
293
+ return Err(NipcError::Truncated);
294
+ }
295
+ validate_lookup_dir(
296
+ buf,
297
+ APPS_LOOKUP_RESP_HDR_SIZE,
298
+ item_count,
299
+ buf.len() - dir_end,
300
+ APPS_LOOKUP_ITEM_HDR_SIZE,
301
+ None,
302
+ )?;
303
+ for i in 0..item_count as usize {
304
+ let base = APPS_LOOKUP_RESP_HDR_SIZE + i * LOOKUP_DIR_ENTRY_SIZE;
305
+ let off = u32_at(buf, base) as usize;
306
+ let len = u32_at(buf, base + 4) as usize;
307
+ decode_apps_item(checked_subslice(buf, dir_end, off, len)?)?;
308
+ }
309
+ Ok(Self {
310
+ layout_version: 1,
311
+ flags: 0,
312
+ item_count,
313
+ generation: u64_at(buf, 8),
314
+ payload: buf,
315
+ })
316
+ }
317
+
318
+ pub fn item(&self, index: u32) -> Result<AppsLookupItemView<'a>, NipcError> {
319
+ if index >= self.item_count {
320
+ return Err(NipcError::OutOfBounds);
321
+ }
322
+ let packed_start = lookup_data_offset(APPS_LOOKUP_RESP_HDR_SIZE, self.item_count)?;
323
+ let base = lookup_dir_entry_offset(APPS_LOOKUP_RESP_HDR_SIZE, index)?;
324
+ let off = u32_at(self.payload, base) as usize;
325
+ let len = u32_at(self.payload, base + 4) as usize;
326
+ decode_apps_item(checked_subslice(self.payload, packed_start, off, len)?)
327
+ }
328
+}
329
+
330
+fn decode_apps_item(item: &[u8]) -> Result<AppsLookupItemView<'_>, NipcError> {
331
+ if item.len() < APPS_LOOKUP_ITEM_HDR_SIZE {
332
+ return Err(NipcError::Truncated);
333
+ }
334
+ let status = u16_at(item, 2);
335
+ let orchestrator = u16_at(item, 4);
336
+ let cgroup_status = u16_at(item, 6);
337
+ let pid = u32_at(item, 8);
338
+ let ppid = u32_at(item, 12);
339
+ let uid = u32_at(item, 16);
340
+ let starttime = u64_at(item, 24);
341
+ let comm_off = u32_at(item, 32) as usize;
342
+ let comm_len = u32_at(item, 36) as usize;
343
+ let path_off = u32_at(item, 40) as usize;
344
+ let path_len = u32_at(item, 44) as usize;
345
+ let name_off = u32_at(item, 48) as usize;
346
+ let name_len = u32_at(item, 52) as usize;
347
+ let label_count = u16_at(item, 56);
348
+
349
+ if u16_at(item, 0) != 1 || u32_at(item, 20) != 0 || u16_at(item, 58) != 0 {
350
+ return Err(NipcError::BadLayout);
351
+ }
352
+ validate_apps_lookup_semantics(
353
+ status,
354
+ cgroup_status,
355
+ orchestrator,
356
+ ppid,
357
+ uid,
358
+ starttime,
359
+ comm_len as u64,
360
+ path_len as u64,
361
+ name_len as u64,
362
+ label_count as u64,
363
+ )?;
364
+
365
+ let (comm, comm_end) = lookup_string(item, APPS_LOOKUP_ITEM_HDR_SIZE, comm_off, comm_len)?;
366
+ let (cgroup_path, path_end) =
367
+ lookup_string(item, APPS_LOOKUP_ITEM_HDR_SIZE, path_off, path_len)?;
368
+ let (cgroup_name, name_end) =
369
+ lookup_string(item, APPS_LOOKUP_ITEM_HDR_SIZE, name_off, name_len)?;
370
+ if overlap(comm_off, comm_end, path_off, path_end)
371
+ || overlap(comm_off, comm_end, name_off, name_end)
372
+ || overlap(path_off, path_end, name_off, name_end)
373
+ {
374
+ return Err(NipcError::BadLayout);
375
+ }
376
+ let label_table_offset = validate_labels(
377
+ item,
378
+ APPS_LOOKUP_ITEM_HDR_SIZE,
379
+ label_count,
380
+ comm_end.max(path_end).max(name_end),
381
+ )?;
382
+ Ok(AppsLookupItemView {
383
+ status,
384
+ orchestrator,
385
+ cgroup_status,
386
+ pid,
387
+ ppid,
388
+ uid,
389
+ starttime,
390
+ comm,
391
+ cgroup_path,
392
+ cgroup_name,
393
+ label_count,
394
+ item,
395
+ label_table_offset,
396
+ })
397
+}
398
+
399
+impl<'a> AppsLookupItemView<'a> {
400
+ pub fn label(&self, index: u32) -> Result<LookupLabelView<'a>, NipcError> {
401
+ label_at(
402
+ self.item,
403
+ APPS_LOOKUP_ITEM_HDR_SIZE,
404
+ self.label_count,
405
+ self.label_table_offset,
406
+ index,
407
+ )
408
+ }
409
+}
410
+
411
+pub struct AppsLookupBuilder<'a> {
412
+ buf: &'a mut [u8],
413
+ generation: u64,
414
+ item_count: u32,
415
+ max_items: u32,
416
+ data_offset: usize,
417
+ error: Option<NipcError>,
418
+}
419
+
420
+impl<'a> AppsLookupBuilder<'a> {
421
+ pub fn new(buf: &'a mut [u8], max_items: u32, generation: u64) -> Self {
422
+ let data_offset = (max_items as usize)
423
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
424
+ .and_then(|v| APPS_LOOKUP_RESP_HDR_SIZE.checked_add(v))
425
+ .expect("AppsLookupBuilder buffer too small");
426
+ assert!(
427
+ buf.len() >= data_offset,
428
+ "AppsLookupBuilder buffer too small"
429
+ );
430
+ Self {
431
+ buf,
432
+ generation,
433
+ item_count: 0,
434
+ max_items,
435
+ data_offset,
436
+ error: None,
437
+ }
438
+ }
439
+
440
+ pub fn set_generation(&mut self, generation: u64) {
441
+ self.generation = generation;
442
+ }
443
+
444
+ #[allow(clippy::too_many_arguments)]
445
+ pub fn add(
446
+ &mut self,
447
+ status: u16,
448
+ cgroup_status: u16,
449
+ orchestrator: u16,
450
+ pid: u32,
451
+ ppid: u32,
452
+ uid: u32,
453
+ starttime: u64,
454
+ comm: &[u8],
455
+ cgroup_path: &[u8],
456
+ cgroup_name: &[u8],
457
+ labels: &[(&[u8], &[u8])],
458
+ ) -> Result<(), NipcError> {
459
+ if self.item_count >= self.max_items {
460
+ self.error = Some(NipcError::Overflow);
461
+ return Err(NipcError::Overflow);
462
+ }
463
+ if let Err(err) = validate_apps_lookup_semantics(
464
+ status,
465
+ cgroup_status,
466
+ orchestrator,
467
+ ppid,
468
+ uid,
469
+ starttime,
470
+ comm.len() as u64,
471
+ cgroup_path.len() as u64,
472
+ cgroup_name.len() as u64,
473
+ labels.len() as u64,
474
+ ) {
475
+ self.error = Some(err);
476
+ return Err(err);
477
+ }
478
+ if source_string_invalid(comm, status == PID_LOOKUP_KNOWN)
479
+ || source_string_invalid(cgroup_path, false)
480
+ || source_string_invalid(cgroup_name, false)
481
+ {
482
+ self.error = Some(NipcError::BadLayout);
483
+ return Err(NipcError::BadLayout);
484
+ }
485
+ let label_count = match checked_u16(labels.len()) {
486
+ Ok(v) => v,
487
+ Err(err) => {
488
+ self.error = Some(err);
489
+ return Err(err);
490
+ }
491
+ };
492
+
493
+ let item_start = align8(self.data_offset);
494
+ let comm_offset = APPS_LOOKUP_ITEM_HDR_SIZE;
495
+ let Some(path_offset) = comm_offset
496
+ .checked_add(comm.len())
497
+ .and_then(|v| v.checked_add(1))
498
+ else {
499
+ self.error = Some(NipcError::Overflow);
500
+ return Err(NipcError::Overflow);
501
+ };
502
+ let Some(name_offset) = path_offset
503
+ .checked_add(cgroup_path.len())
504
+ .and_then(|v| v.checked_add(1))
505
+ else {
506
+ self.error = Some(NipcError::Overflow);
507
+ return Err(NipcError::Overflow);
508
+ };
509
+ let Some(fixed_end) = name_offset
510
+ .checked_add(cgroup_name.len())
511
+ .and_then(|v| v.checked_add(1))
512
+ else {
513
+ self.error = Some(NipcError::Overflow);
514
+ return Err(NipcError::Overflow);
515
+ };
516
+ let (table_start, table_bytes, mut item_size) = label_layout(fixed_end, labels)?;
517
+ let item_end = item_start
518
+ .checked_add(item_size)
519
+ .ok_or(NipcError::Overflow)?;
520
+ if item_end > self.buf.len() {
521
+ self.error = Some(NipcError::Overflow);
522
+ return Err(NipcError::Overflow);
523
+ }
524
+ if item_start > self.data_offset {
525
+ self.buf[self.data_offset..item_start].fill(0);
526
+ }
527
+ let item = &mut self.buf[item_start..item_end];
528
+ if let Err(err) = write_apps_item_header(
529
+ item,
530
+ status,
531
+ orchestrator,
532
+ cgroup_status,
533
+ pid,
534
+ ppid,
535
+ uid,
536
+ starttime,
537
+ comm_offset,
538
+ comm.len(),
539
+ path_offset,
540
+ cgroup_path.len(),
541
+ name_offset,
542
+ cgroup_name.len(),
543
+ label_count,
544
+ ) {
545
+ self.error = Some(err);
546
+ return Err(err);
547
+ }
548
+ item[comm_offset..comm_offset + comm.len()].copy_from_slice(comm);
549
+ item[comm_offset + comm.len()] = 0;
550
+ item[path_offset..path_offset + cgroup_path.len()].copy_from_slice(cgroup_path);
551
+ item[path_offset + cgroup_path.len()] = 0;
552
+ item[name_offset..name_offset + cgroup_name.len()].copy_from_slice(cgroup_name);
553
+ item[name_offset + cgroup_name.len()] = 0;
554
+ if !labels.is_empty() {
555
+ item[fixed_end..table_start].fill(0);
556
+ item_size = write_lookup_labels(item, table_start, table_bytes, labels)?;
557
+ }
558
+ let dir_offset = (self.item_count as usize)
559
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
560
+ .ok_or(NipcError::Overflow)?;
561
+ let dir = APPS_LOOKUP_RESP_HDR_SIZE
562
+ .checked_add(dir_offset)
563
+ .ok_or(NipcError::Overflow)?;
564
+ put_u32(self.buf, dir, checked_u32(item_start)?);
565
+ put_u32(self.buf, dir + 4, checked_u32(item_size)?);
566
+ self.data_offset = item_end;
567
+ self.item_count += 1;
568
+ Ok(())
569
+ }
570
+
571
+ pub fn finish(self) -> Result<usize, NipcError> {
572
+ finish_lookup_response(
573
+ self.buf,
574
+ APPS_LOOKUP_RESP_HDR_SIZE,
575
+ self.item_count,
576
+ self.data_offset,
577
+ self.generation,
578
+ )
579
+ }
580
+
581
+ pub fn error(&self) -> Option<NipcError> {
582
+ self.error
583
+ }
584
+
585
+ pub fn item_count(&self) -> u32 {
586
+ self.item_count
587
+ }
588
+}
589
+
590
+#[allow(clippy::too_many_arguments)]
591
+fn write_apps_item_header(
592
+ item: &mut [u8],
593
+ status: u16,
594
+ orchestrator: u16,
595
+ cgroup_status: u16,
596
+ pid: u32,
597
+ ppid: u32,
598
+ uid: u32,
599
+ starttime: u64,
600
+ comm_off: usize,
601
+ comm_len: usize,
602
+ path_off: usize,
603
+ path_len: usize,
604
+ name_off: usize,
605
+ name_len: usize,
606
+ label_count: u16,
607
+) -> Result<(), NipcError> {
608
+ put_u16(item, 0, 1);
609
+ put_u16(item, 2, status);
610
+ put_u16(item, 4, orchestrator);
611
+ put_u16(item, 6, cgroup_status);
612
+ put_u32(item, 8, pid);
613
+ put_u32(item, 12, ppid);
614
+ put_u32(item, 16, uid);
615
+ put_u32(item, 20, 0);
616
+ put_u64(item, 24, starttime);
617
+ put_u32(item, 32, checked_u32(comm_off)?);
618
+ put_u32(item, 36, checked_u32(comm_len)?);
619
+ put_u32(item, 40, checked_u32(path_off)?);
620
+ put_u32(item, 44, checked_u32(path_len)?);
621
+ put_u32(item, 48, checked_u32(name_off)?);
622
+ put_u32(item, 52, checked_u32(name_len)?);
623
+ put_u16(item, 56, label_count);
624
+ put_u16(item, 58, 0);
625
+ Ok(())
626
+}
627
+
628
+pub fn dispatch_apps_lookup<F>(req: &[u8], resp: &mut [u8], handler: F) -> Result<usize, NipcError>
629
+where
630
+ F: FnOnce(&AppsLookupRequestView, &mut AppsLookupBuilder) -> bool,
631
+{
632
+ let request = AppsLookupRequestView::decode(req)?;
633
+ let min_required = lookup_data_offset(APPS_LOOKUP_RESP_HDR_SIZE, request.item_count)
634
+ .map_err(|_| NipcError::Overflow)?;
635
+ if resp.len() < min_required {
636
+ return Err(NipcError::Overflow);
637
+ }
638
+ let mut builder = AppsLookupBuilder::new(resp, request.item_count, 0);
639
+ if !handler(&request, &mut builder) {
640
+ return Err(builder.error().unwrap_or(NipcError::BadLayout));
641
+ }
642
+ if let Some(err) = builder.error() {
643
+ return Err(err);
644
+ }
645
+ if builder.item_count != request.item_count {
646
+ return Err(NipcError::BadItemCount);
647
+ }
648
+ builder.finish()
649
+}
650
+
651
+#[cfg(test)]
652
+mod tests {
653
+ use super::super::common::{put_u16, put_u32, response_item_bounds, LOOKUP_DIR_ENTRY_SIZE};
654
+ use super::*;
655
+ use crate::protocol::ORCHESTRATOR_DOCKER;
656
+
657
+ #[test]
658
+ fn apps_lookup_response_variants_roundtrip() {
659
+ let mut buf = [0u8; 1024];
660
+ let mut b = AppsLookupBuilder::new(&mut buf, 4, 7);
661
+ b.add(
662
+ PID_LOOKUP_KNOWN,
663
+ APPS_CGROUP_KNOWN,
664
+ ORCHESTRATOR_DOCKER,
665
+ 123,
666
+ 1,
667
+ 1000,
668
+ 42,
669
+ b"nginx",
670
+ b"/docker/abc",
671
+ b"container-a",
672
+ &[(b"image".as_slice(), b"nginx:latest".as_slice())],
673
+ )
674
+ .unwrap();
675
+ b.add(
676
+ PID_LOOKUP_KNOWN,
677
+ APPS_CGROUP_UNKNOWN_RETRY_LATER,
678
+ 0,
679
+ 125,
680
+ 1,
681
+ 0,
682
+ 44,
683
+ b"worker",
684
+ b"",
685
+ b"",
686
+ &[],
687
+ )
688
+ .unwrap();
689
+ b.add(
690
+ PID_LOOKUP_KNOWN,
691
+ APPS_CGROUP_HOST_ROOT,
692
+ 0,
693
+ 124,
694
+ 1,
695
+ 0,
696
+ 43,
697
+ b"sshd",
698
+ b"",
699
+ b"",
700
+ &[],
701
+ )
702
+ .unwrap();
703
+ b.add(
704
+ PID_LOOKUP_UNKNOWN,
705
+ APPS_CGROUP_KNOWN,
706
+ 0,
707
+ 0,
708
+ 0,
709
+ NIPC_UID_UNSET,
710
+ 0,
711
+ b"",
712
+ b"",
713
+ b"",
714
+ &[],
715
+ )
716
+ .unwrap();
717
+ let n = b.finish().unwrap();
718
+ let view = AppsLookupResponseView::decode(&buf[..n]).unwrap();
719
+ assert_eq!(view.item_count, 4);
720
+ assert_eq!(view.item(0).unwrap().comm.as_bytes(), b"nginx");
721
+ assert_eq!(
722
+ view.item(1).unwrap().cgroup_status,
723
+ APPS_CGROUP_UNKNOWN_RETRY_LATER
724
+ );
725
+ assert_eq!(view.item(1).unwrap().cgroup_path.as_bytes(), b"");
726
+ assert_eq!(view.item(2).unwrap().cgroup_status, APPS_CGROUP_HOST_ROOT);
727
+ assert_eq!(view.item(3).unwrap().status, PID_LOOKUP_UNKNOWN);
728
+ }
729
+
730
+ #[test]
731
+ fn apps_lookup_comm_boundary() {
732
+ let mut buf = [0u8; 256];
733
+ let mut b = AppsLookupBuilder::new(&mut buf, 1, 0);
734
+ assert!(b
735
+ .add(
736
+ PID_LOOKUP_KNOWN,
737
+ APPS_CGROUP_HOST_ROOT,
738
+ 0,
739
+ 1,
740
+ 0,
741
+ 0,
742
+ 1,
743
+ b"123456789012345",
744
+ b"",
745
+ b"",
746
+ &[],
747
+ )
748
+ .is_ok());
749
+ let mut b = AppsLookupBuilder::new(&mut buf, 1, 0);
750
+ assert_eq!(
751
+ b.add(
752
+ PID_LOOKUP_KNOWN,
753
+ APPS_CGROUP_HOST_ROOT,
754
+ 0,
755
+ 1,
756
+ 0,
757
+ 0,
758
+ 1,
759
+ b"1234567890123456",
760
+ b"",
761
+ b"",
762
+ &[],
763
+ )
764
+ .unwrap_err(),
765
+ NipcError::BadLayout
766
+ );
767
+ }
768
+
769
+ fn apps_lookup_host_root_response() -> Vec<u8> {
770
+ let mut buf = vec![0u8; 256];
771
+ let mut b = AppsLookupBuilder::new(&mut buf, 1, 1);
772
+ b.add(
773
+ PID_LOOKUP_KNOWN,
774
+ APPS_CGROUP_HOST_ROOT,
775
+ 0,
776
+ 123,
777
+ 1,
778
+ 1000,
779
+ 42,
780
+ b"a",
781
+ b"",
782
+ b"",
783
+ &[],
784
+ )
785
+ .unwrap();
786
+ let n = b.finish().unwrap();
787
+ buf.truncate(n);
788
+ buf
789
+ }
790
+
791
+ #[test]
792
+ fn apps_lookup_empty_request_response() {
793
+ let mut buf = [0u8; 64];
794
+ let n = encode_apps_lookup_request(&[], &mut buf).unwrap();
795
+ let view = AppsLookupRequestView::decode(&buf[..n]).unwrap();
796
+ assert_eq!(view.item_count, 0);
797
+
798
+ let mut abuf = [0u8; 64];
799
+ let b = AppsLookupBuilder::new(&mut abuf, 0, 10);
800
+ let n = b.finish().unwrap();
801
+ let view = AppsLookupResponseView::decode(&abuf[..n]).unwrap();
802
+ assert_eq!(view.item_count, 0);
803
+ assert_eq!(view.generation, 10);
804
+ }
805
+
806
+ #[test]
807
+ fn apps_lookup_dispatch_rejects_short_response_buffer() {
808
+ let mut req = [0u8; 64];
809
+ let n = encode_apps_lookup_request(&[1234], &mut req).unwrap();
810
+ let mut resp = vec![0u8; APPS_LOOKUP_RESP_HDR_SIZE + LOOKUP_DIR_ENTRY_SIZE - 1];
811
+ assert_eq!(
812
+ dispatch_apps_lookup(&req[..n], &mut resp, |_, _| {
813
+ panic!("handler must not run with undersized response buffer")
814
+ })
815
+ .unwrap_err(),
816
+ NipcError::Overflow
817
+ );
818
+ }
819
+
820
+ #[test]
821
+ fn apps_lookup_request_rejects_bad_layouts() {
822
+ let mut buf = [0u8; 128];
823
+ let n = encode_apps_lookup_request(&[1234], &mut buf).unwrap();
824
+ let mut bad = buf[..n].to_vec();
825
+ put_u32(&mut bad, 8, 1);
826
+ assert_eq!(
827
+ AppsLookupRequestView::decode(&bad).unwrap_err(),
828
+ NipcError::BadLayout
829
+ );
830
+
831
+ bad.copy_from_slice(&buf[..n]);
832
+ put_u32(&mut bad, APPS_LOOKUP_REQ_HDR_SIZE, 1);
833
+ assert_eq!(
834
+ AppsLookupRequestView::decode(&bad).unwrap_err(),
835
+ NipcError::BadAlignment
836
+ );
837
+
838
+ bad.copy_from_slice(&buf[..n]);
839
+ put_u32(&mut bad, APPS_LOOKUP_REQ_HDR_SIZE + 4, 7);
840
+ assert_eq!(
841
+ AppsLookupRequestView::decode(&bad).unwrap_err(),
842
+ NipcError::BadLayout
843
+ );
844
+
845
+ bad.copy_from_slice(&buf[..n]);
846
+ put_u32(&mut bad, APPS_LOOKUP_REQ_HDR_SIZE, 8);
847
+ assert_eq!(
848
+ AppsLookupRequestView::decode(&bad).unwrap_err(),
849
+ NipcError::OutOfBounds
850
+ );
851
+
852
+ bad.copy_from_slice(&buf[..n]);
853
+ put_u32(
854
+ &mut bad,
855
+ APPS_LOOKUP_REQ_HDR_SIZE + LOOKUP_DIR_ENTRY_SIZE + 4,
856
+ 1,
857
+ );
858
+ assert_eq!(
859
+ AppsLookupRequestView::decode(&bad).unwrap_err(),
860
+ NipcError::BadLayout
861
+ );
862
+ }
863
+
864
+ #[test]
865
+ fn apps_lookup_response_rejects_bad_layouts() {
866
+ let buf = apps_lookup_host_root_response();
867
+
868
+ let mut bad = buf.clone();
869
+ let (item_start, _) = response_item_bounds(&bad, APPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
870
+ put_u16(&mut bad, item_start + 2, 99);
871
+ assert_eq!(
872
+ AppsLookupResponseView::decode(&bad).unwrap_err(),
873
+ NipcError::BadLayout
874
+ );
875
+
876
+ bad = buf.clone();
877
+ let (item_start, _) = response_item_bounds(&bad, APPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
878
+ put_u16(&mut bad, item_start + 6, 99);
879
+ assert_eq!(
880
+ AppsLookupResponseView::decode(&bad).unwrap_err(),
881
+ NipcError::BadLayout
882
+ );
883
+
884
+ bad = buf.clone();
885
+ let (item_start, _) = response_item_bounds(&bad, APPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
886
+ put_u32(&mut bad, item_start + 36, 0);
887
+ assert_eq!(
888
+ AppsLookupResponseView::decode(&bad).unwrap_err(),
889
+ NipcError::BadLayout
890
+ );
891
+ }
892
+}
src/crates/netipc/src/protocol/lookup/cgroups_lookup.rs
new
+755
@@ -0,0 +1,755 @@
1
+//! CGROUPS_LOOKUP codec.
2
+
3
+use super::common::*;
4
+use crate::protocol::{align8, NipcError, StrView};
5
+
6
+pub const CGROUP_LOOKUP_KNOWN: u16 = 0;
7
+pub const CGROUP_LOOKUP_UNKNOWN_RETRY_LATER: u16 = 1;
8
+pub const CGROUP_LOOKUP_UNKNOWN_PERMANENT: u16 = 2;
9
+
10
+pub const CGROUPS_LOOKUP_REQ_HDR_SIZE: usize = 16;
11
+pub const CGROUPS_LOOKUP_RESP_HDR_SIZE: usize = 16;
12
+pub const CGROUPS_LOOKUP_ITEM_HDR_SIZE: usize = 28;
13
+
14
+#[derive(Debug)]
15
+pub struct CgroupsLookupRequestView<'a> {
16
+ pub item_count: u32,
17
+ payload: &'a [u8],
18
+}
19
+
20
+#[derive(Debug)]
21
+pub struct CgroupsLookupResponseView<'a> {
22
+ pub layout_version: u16,
23
+ pub flags: u16,
24
+ pub item_count: u32,
25
+ pub generation: u64,
26
+ payload: &'a [u8],
27
+}
28
+
29
+#[derive(Debug, Clone, Copy, PartialEq)]
30
+pub struct CgroupsLookupItemView<'a> {
31
+ pub status: u16,
32
+ pub orchestrator: u16,
33
+ pub path: StrView<'a>,
34
+ pub name: StrView<'a>,
35
+ pub label_count: u16,
36
+ item: &'a [u8],
37
+ label_table_offset: usize,
38
+}
39
+
40
+fn validate_cgroups_lookup_semantics(
41
+ status: u16,
42
+ orchestrator: u16,
43
+ path_len: u64,
44
+ name_len: u64,
45
+ label_count: u64,
46
+) -> Result<(), NipcError> {
47
+ if status != CGROUP_LOOKUP_KNOWN
48
+ && status != CGROUP_LOOKUP_UNKNOWN_RETRY_LATER
49
+ && status != CGROUP_LOOKUP_UNKNOWN_PERMANENT
50
+ {
51
+ return Err(NipcError::BadLayout);
52
+ }
53
+ if path_len == 0 {
54
+ return Err(NipcError::BadLayout);
55
+ }
56
+ if status != CGROUP_LOOKUP_KNOWN && (orchestrator != 0 || name_len != 0 || label_count != 0) {
57
+ return Err(NipcError::BadLayout);
58
+ }
59
+ Ok(())
60
+}
61
+
62
+pub fn encode_cgroups_lookup_request(paths: &[&[u8]], buf: &mut [u8]) -> Result<usize, NipcError> {
63
+ let count = paths.len();
64
+ let dir_size = count
65
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
66
+ .ok_or(NipcError::Overflow)?;
67
+ let packed_start = CGROUPS_LOOKUP_REQ_HDR_SIZE
68
+ .checked_add(dir_size)
69
+ .ok_or(NipcError::Overflow)?;
70
+ if buf.len() < packed_start {
71
+ return Err(NipcError::Overflow);
72
+ }
73
+
74
+ let mut data = packed_start;
75
+ for (i, path) in paths.iter().enumerate() {
76
+ if source_string_invalid(path, true) {
77
+ return Err(NipcError::BadLayout);
78
+ }
79
+ let aligned = align8(data);
80
+ let key_len = path.len().checked_add(1).ok_or(NipcError::Overflow)?;
81
+ let end = aligned.checked_add(key_len).ok_or(NipcError::Overflow)?;
82
+ if end > buf.len() {
83
+ return Err(NipcError::Overflow);
84
+ }
85
+ if aligned > data {
86
+ buf[data..aligned].fill(0);
87
+ }
88
+
89
+ let dir = CGROUPS_LOOKUP_REQ_HDR_SIZE + i * LOOKUP_DIR_ENTRY_SIZE;
90
+ put_u32(buf, dir, checked_u32(aligned - packed_start)?);
91
+ put_u32(buf, dir + 4, checked_u32(key_len)?);
92
+ buf[aligned..aligned + path.len()].copy_from_slice(path);
93
+ buf[aligned + path.len()] = 0;
94
+ data = end;
95
+ }
96
+
97
+ put_u16(buf, 0, 1);
98
+ put_u16(buf, 2, 0);
99
+ put_u32(buf, 4, checked_u32(count)?);
100
+ put_u32(buf, 8, 0);
101
+ put_u32(buf, 12, 0);
102
+ Ok(data)
103
+}
104
+
105
+impl<'a> CgroupsLookupRequestView<'a> {
106
+ pub fn decode(buf: &'a [u8]) -> Result<Self, NipcError> {
107
+ if buf.len() < CGROUPS_LOOKUP_REQ_HDR_SIZE {
108
+ return Err(NipcError::Truncated);
109
+ }
110
+ if u16_at(buf, 0) != 1 || u16_at(buf, 2) != 0 || u32_at(buf, 8) != 0 || u32_at(buf, 12) != 0
111
+ {
112
+ return Err(NipcError::BadLayout);
113
+ }
114
+ let item_count = u32_at(buf, 4);
115
+ let dir_size = (item_count as usize)
116
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
117
+ .ok_or(NipcError::BadItemCount)?;
118
+ let dir_end = CGROUPS_LOOKUP_REQ_HDR_SIZE
119
+ .checked_add(dir_size)
120
+ .ok_or(NipcError::BadItemCount)?;
121
+ if dir_end > buf.len() {
122
+ return Err(NipcError::Truncated);
123
+ }
124
+ let packed_len = buf.len() - dir_end;
125
+ validate_lookup_dir(
126
+ buf,
127
+ CGROUPS_LOOKUP_REQ_HDR_SIZE,
128
+ item_count,
129
+ packed_len,
130
+ 2,
131
+ None,
132
+ )?;
133
+ for i in 0..item_count as usize {
134
+ let base = CGROUPS_LOOKUP_REQ_HDR_SIZE + i * LOOKUP_DIR_ENTRY_SIZE;
135
+ let off = u32_at(buf, base) as usize;
136
+ let len = u32_at(buf, base + 4) as usize;
137
+ let key = checked_subslice(buf, dir_end, off, len)?;
138
+ if key[len - 1] != 0 {
139
+ return Err(NipcError::MissingNul);
140
+ }
141
+ if key[..len - 1].contains(&0) {
142
+ return Err(NipcError::BadLayout);
143
+ }
144
+ }
145
+ Ok(Self {
146
+ item_count,
147
+ payload: buf,
148
+ })
149
+ }
150
+
151
+ pub fn item(&self, index: u32) -> Result<StrView<'a>, NipcError> {
152
+ if index >= self.item_count {
153
+ return Err(NipcError::OutOfBounds);
154
+ }
155
+ let dir_size = (self.item_count as usize)
156
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
157
+ .ok_or(NipcError::BadItemCount)?;
158
+ let packed_start = CGROUPS_LOOKUP_REQ_HDR_SIZE
159
+ .checked_add(dir_size)
160
+ .ok_or(NipcError::BadItemCount)?;
161
+ let base = CGROUPS_LOOKUP_REQ_HDR_SIZE
162
+ .checked_add(
163
+ (index as usize)
164
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
165
+ .ok_or(NipcError::BadItemCount)?,
166
+ )
167
+ .ok_or(NipcError::BadItemCount)?;
168
+ let off = u32_at(self.payload, base) as usize;
169
+ let len = u32_at(self.payload, base + 4) as usize;
170
+ Ok(StrView {
171
+ bytes: checked_subslice(self.payload, packed_start, off, len)?,
172
+ len: (len - 1) as u32,
173
+ })
174
+ }
175
+}
176
+
177
+impl<'a> CgroupsLookupResponseView<'a> {
178
+ pub fn decode(buf: &'a [u8]) -> Result<Self, NipcError> {
179
+ if buf.len() < CGROUPS_LOOKUP_RESP_HDR_SIZE {
180
+ return Err(NipcError::Truncated);
181
+ }
182
+ if u16_at(buf, 0) != 1 || u16_at(buf, 2) != 0 {
183
+ return Err(NipcError::BadLayout);
184
+ }
185
+ let item_count = u32_at(buf, 4);
186
+ let dir_size = (item_count as usize)
187
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
188
+ .ok_or(NipcError::BadItemCount)?;
189
+ let dir_end = CGROUPS_LOOKUP_RESP_HDR_SIZE
190
+ .checked_add(dir_size)
191
+ .ok_or(NipcError::BadItemCount)?;
192
+ if dir_end > buf.len() {
193
+ return Err(NipcError::Truncated);
194
+ }
195
+ validate_lookup_dir(
196
+ buf,
197
+ CGROUPS_LOOKUP_RESP_HDR_SIZE,
198
+ item_count,
199
+ buf.len() - dir_end,
200
+ CGROUPS_LOOKUP_ITEM_HDR_SIZE,
201
+ None,
202
+ )?;
203
+ for i in 0..item_count as usize {
204
+ let base = CGROUPS_LOOKUP_RESP_HDR_SIZE + i * LOOKUP_DIR_ENTRY_SIZE;
205
+ let off = u32_at(buf, base) as usize;
206
+ let len = u32_at(buf, base + 4) as usize;
207
+ decode_cgroups_item(checked_subslice(buf, dir_end, off, len)?)?;
208
+ }
209
+ Ok(Self {
210
+ layout_version: 1,
211
+ flags: 0,
212
+ item_count,
213
+ generation: u64_at(buf, 8),
214
+ payload: buf,
215
+ })
216
+ }
217
+
218
+ pub fn item(&self, index: u32) -> Result<CgroupsLookupItemView<'a>, NipcError> {
219
+ if index >= self.item_count {
220
+ return Err(NipcError::OutOfBounds);
221
+ }
222
+ let packed_start = lookup_data_offset(CGROUPS_LOOKUP_RESP_HDR_SIZE, self.item_count)?;
223
+ let base = lookup_dir_entry_offset(CGROUPS_LOOKUP_RESP_HDR_SIZE, index)?;
224
+ let off = u32_at(self.payload, base) as usize;
225
+ let len = u32_at(self.payload, base + 4) as usize;
226
+ decode_cgroups_item(checked_subslice(self.payload, packed_start, off, len)?)
227
+ }
228
+}
229
+
230
+fn decode_cgroups_item(item: &[u8]) -> Result<CgroupsLookupItemView<'_>, NipcError> {
231
+ if item.len() < CGROUPS_LOOKUP_ITEM_HDR_SIZE {
232
+ return Err(NipcError::Truncated);
233
+ }
234
+ let status = u16_at(item, 2);
235
+ let orchestrator = u16_at(item, 4);
236
+ let path_off = u32_at(item, 8) as usize;
237
+ let path_len = u32_at(item, 12) as usize;
238
+ let name_off = u32_at(item, 16) as usize;
239
+ let name_len = u32_at(item, 20) as usize;
240
+ let label_count = u16_at(item, 24);
241
+
242
+ if u16_at(item, 0) != 1 || u16_at(item, 6) != 0 || u16_at(item, 26) != 0 {
243
+ return Err(NipcError::BadLayout);
244
+ }
245
+ validate_cgroups_lookup_semantics(
246
+ status,
247
+ orchestrator,
248
+ path_len as u64,
249
+ name_len as u64,
250
+ label_count as u64,
251
+ )?;
252
+
253
+ let (path, path_end) = lookup_string(item, CGROUPS_LOOKUP_ITEM_HDR_SIZE, path_off, path_len)?;
254
+ let (name, name_end) = lookup_string(item, CGROUPS_LOOKUP_ITEM_HDR_SIZE, name_off, name_len)?;
255
+ if overlap(path_off, path_end, name_off, name_end) {
256
+ return Err(NipcError::BadLayout);
257
+ }
258
+ let label_table_offset = validate_labels(
259
+ item,
260
+ CGROUPS_LOOKUP_ITEM_HDR_SIZE,
261
+ label_count,
262
+ path_end.max(name_end),
263
+ )?;
264
+ Ok(CgroupsLookupItemView {
265
+ status,
266
+ orchestrator,
267
+ path,
268
+ name,
269
+ label_count,
270
+ item,
271
+ label_table_offset,
272
+ })
273
+}
274
+
275
+impl<'a> CgroupsLookupItemView<'a> {
276
+ pub fn label(&self, index: u32) -> Result<LookupLabelView<'a>, NipcError> {
277
+ label_at(
278
+ self.item,
279
+ CGROUPS_LOOKUP_ITEM_HDR_SIZE,
280
+ self.label_count,
281
+ self.label_table_offset,
282
+ index,
283
+ )
284
+ }
285
+}
286
+
287
+pub struct CgroupsLookupBuilder<'a> {
288
+ buf: &'a mut [u8],
289
+ generation: u64,
290
+ item_count: u32,
291
+ max_items: u32,
292
+ data_offset: usize,
293
+ error: Option<NipcError>,
294
+}
295
+
296
+impl<'a> CgroupsLookupBuilder<'a> {
297
+ pub fn new(buf: &'a mut [u8], max_items: u32, generation: u64) -> Self {
298
+ let data_offset = (max_items as usize)
299
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
300
+ .and_then(|v| CGROUPS_LOOKUP_RESP_HDR_SIZE.checked_add(v))
301
+ .expect("CgroupsLookupBuilder buffer too small");
302
+ assert!(
303
+ buf.len() >= data_offset,
304
+ "CgroupsLookupBuilder buffer too small"
305
+ );
306
+ Self {
307
+ buf,
308
+ generation,
309
+ item_count: 0,
310
+ max_items,
311
+ data_offset,
312
+ error: None,
313
+ }
314
+ }
315
+
316
+ pub fn set_generation(&mut self, generation: u64) {
317
+ self.generation = generation;
318
+ }
319
+
320
+ pub fn add(
321
+ &mut self,
322
+ status: u16,
323
+ orchestrator: u16,
324
+ path: &[u8],
325
+ name: &[u8],
326
+ labels: &[(&[u8], &[u8])],
327
+ ) -> Result<(), NipcError> {
328
+ if self.item_count >= self.max_items {
329
+ self.error = Some(NipcError::Overflow);
330
+ return Err(NipcError::Overflow);
331
+ }
332
+ if let Err(err) = validate_cgroups_lookup_semantics(
333
+ status,
334
+ orchestrator,
335
+ path.len() as u64,
336
+ name.len() as u64,
337
+ labels.len() as u64,
338
+ ) {
339
+ self.error = Some(err);
340
+ return Err(err);
341
+ }
342
+ if source_string_invalid(path, true) || source_string_invalid(name, false) {
343
+ self.error = Some(NipcError::BadLayout);
344
+ return Err(NipcError::BadLayout);
345
+ }
346
+ let label_count = match checked_u16(labels.len()) {
347
+ Ok(v) => v,
348
+ Err(err) => {
349
+ self.error = Some(err);
350
+ return Err(err);
351
+ }
352
+ };
353
+
354
+ let item_start = align8(self.data_offset);
355
+ let path_offset = CGROUPS_LOOKUP_ITEM_HDR_SIZE;
356
+ let Some(name_offset) = path_offset
357
+ .checked_add(path.len())
358
+ .and_then(|v| v.checked_add(1))
359
+ else {
360
+ self.error = Some(NipcError::Overflow);
361
+ return Err(NipcError::Overflow);
362
+ };
363
+ let Some(fixed_end) = name_offset
364
+ .checked_add(name.len())
365
+ .and_then(|v| v.checked_add(1))
366
+ else {
367
+ self.error = Some(NipcError::Overflow);
368
+ return Err(NipcError::Overflow);
369
+ };
370
+ let (table_start, table_bytes, mut item_size) = label_layout(fixed_end, labels)?;
371
+ let item_end = item_start
372
+ .checked_add(item_size)
373
+ .ok_or(NipcError::Overflow)?;
374
+ if item_end > self.buf.len() {
375
+ self.error = Some(NipcError::Overflow);
376
+ return Err(NipcError::Overflow);
377
+ }
378
+ if item_start > self.data_offset {
379
+ self.buf[self.data_offset..item_start].fill(0);
380
+ }
381
+ let item = &mut self.buf[item_start..item_end];
382
+ if let Err(err) = write_cgroups_item_header(
383
+ item,
384
+ status,
385
+ orchestrator,
386
+ path_offset,
387
+ path.len(),
388
+ name_offset,
389
+ name.len(),
390
+ label_count,
391
+ ) {
392
+ self.error = Some(err);
393
+ return Err(err);
394
+ }
395
+ item[path_offset..path_offset + path.len()].copy_from_slice(path);
396
+ item[path_offset + path.len()] = 0;
397
+ item[name_offset..name_offset + name.len()].copy_from_slice(name);
398
+ item[name_offset + name.len()] = 0;
399
+ if !labels.is_empty() {
400
+ item[fixed_end..table_start].fill(0);
401
+ item_size = write_lookup_labels(item, table_start, table_bytes, labels)?;
402
+ }
403
+ let dir_offset = (self.item_count as usize)
404
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
405
+ .ok_or(NipcError::Overflow)?;
406
+ let dir = CGROUPS_LOOKUP_RESP_HDR_SIZE
407
+ .checked_add(dir_offset)
408
+ .ok_or(NipcError::Overflow)?;
409
+ put_u32(self.buf, dir, checked_u32(item_start)?);
410
+ put_u32(self.buf, dir + 4, checked_u32(item_size)?);
411
+ self.data_offset = item_end;
412
+ self.item_count += 1;
413
+ Ok(())
414
+ }
415
+
416
+ pub fn finish(self) -> Result<usize, NipcError> {
417
+ finish_lookup_response(
418
+ self.buf,
419
+ CGROUPS_LOOKUP_RESP_HDR_SIZE,
420
+ self.item_count,
421
+ self.data_offset,
422
+ self.generation,
423
+ )
424
+ }
425
+
426
+ pub fn error(&self) -> Option<NipcError> {
427
+ self.error
428
+ }
429
+
430
+ pub fn item_count(&self) -> u32 {
431
+ self.item_count
432
+ }
433
+}
434
+
435
+fn write_cgroups_item_header(
436
+ item: &mut [u8],
437
+ status: u16,
438
+ orchestrator: u16,
439
+ path_off: usize,
440
+ path_len: usize,
441
+ name_off: usize,
442
+ name_len: usize,
443
+ label_count: u16,
444
+) -> Result<(), NipcError> {
445
+ put_u16(item, 0, 1);
446
+ put_u16(item, 2, status);
447
+ put_u16(item, 4, orchestrator);
448
+ put_u16(item, 6, 0);
449
+ put_u32(item, 8, checked_u32(path_off)?);
450
+ put_u32(item, 12, checked_u32(path_len)?);
451
+ put_u32(item, 16, checked_u32(name_off)?);
452
+ put_u32(item, 20, checked_u32(name_len)?);
453
+ put_u16(item, 24, label_count);
454
+ put_u16(item, 26, 0);
455
+ Ok(())
456
+}
457
+
458
+pub fn dispatch_cgroups_lookup<F>(
459
+ req: &[u8],
460
+ resp: &mut [u8],
461
+ handler: F,
462
+) -> Result<usize, NipcError>
463
+where
464
+ F: FnOnce(&CgroupsLookupRequestView, &mut CgroupsLookupBuilder) -> bool,
465
+{
466
+ let request = CgroupsLookupRequestView::decode(req)?;
467
+ let min_required = lookup_data_offset(CGROUPS_LOOKUP_RESP_HDR_SIZE, request.item_count)
468
+ .map_err(|_| NipcError::Overflow)?;
469
+ if resp.len() < min_required {
470
+ return Err(NipcError::Overflow);
471
+ }
472
+ let mut builder = CgroupsLookupBuilder::new(resp, request.item_count, 0);
473
+ if !handler(&request, &mut builder) {
474
+ return Err(builder.error().unwrap_or(NipcError::BadLayout));
475
+ }
476
+ if let Some(err) = builder.error() {
477
+ return Err(err);
478
+ }
479
+ if builder.item_count != request.item_count {
480
+ return Err(NipcError::BadItemCount);
481
+ }
482
+ builder.finish()
483
+}
484
+
485
+#[cfg(test)]
486
+mod tests {
487
+ use super::super::common::{
488
+ put_u16, put_u32, response_item_bounds, u32_at, LOOKUP_DIR_ENTRY_SIZE,
489
+ };
490
+ use super::*;
491
+ use crate::protocol::{align8, ORCHESTRATOR_K8S};
492
+
493
+ #[test]
494
+ fn cgroups_lookup_request_roundtrip() {
495
+ let mut buf = [0u8; 128];
496
+ let n = encode_cgroups_lookup_request(&[b"/a/b", b"/c"], &mut buf).unwrap();
497
+ let view = CgroupsLookupRequestView::decode(&buf[..n]).unwrap();
498
+ assert_eq!(view.item_count, 2);
499
+ assert_eq!(view.item(0).unwrap().as_bytes(), b"/a/b");
500
+ assert_eq!(view.item(1).unwrap().as_bytes(), b"/c");
501
+ }
502
+
503
+ #[test]
504
+ fn cgroups_lookup_response_labels_roundtrip() {
505
+ let mut buf = [0u8; 512];
506
+ let mut b = CgroupsLookupBuilder::new(&mut buf, 1, 99);
507
+ b.add(
508
+ CGROUP_LOOKUP_KNOWN,
509
+ ORCHESTRATOR_K8S,
510
+ b"/kubepod",
511
+ b"pod-a",
512
+ &[(b"namespace".as_slice(), b"default".as_slice())],
513
+ )
514
+ .unwrap();
515
+ let n = b.finish().unwrap();
516
+ let view = CgroupsLookupResponseView::decode(&buf[..n]).unwrap();
517
+ assert_eq!(view.generation, 99);
518
+ let item = view.item(0).unwrap();
519
+ assert_eq!(item.path.as_bytes(), b"/kubepod");
520
+ assert_eq!(item.name.as_bytes(), b"pod-a");
521
+ let label = item.label(0).unwrap();
522
+ assert_eq!(label.key.as_bytes(), b"namespace");
523
+ assert_eq!(label.value.as_bytes(), b"default");
524
+ }
525
+
526
+ fn cgroups_lookup_labeled_response() -> Vec<u8> {
527
+ let mut buf = vec![0u8; 512];
528
+ let mut b = CgroupsLookupBuilder::new(&mut buf, 1, 1);
529
+ b.add(
530
+ CGROUP_LOOKUP_KNOWN,
531
+ ORCHESTRATOR_K8S,
532
+ b"/x",
533
+ b"n",
534
+ &[(b"k".as_slice(), b"v".as_slice())],
535
+ )
536
+ .unwrap();
537
+ let n = b.finish().unwrap();
538
+ buf.truncate(n);
539
+ buf
540
+ }
541
+
542
+ #[test]
543
+ fn cgroups_lookup_empty_request_response() {
544
+ let mut buf = [0u8; 64];
545
+ let n = encode_cgroups_lookup_request(&[], &mut buf).unwrap();
546
+ let view = CgroupsLookupRequestView::decode(&buf[..n]).unwrap();
547
+ assert_eq!(view.item_count, 0);
548
+
549
+ let mut cbuf = [0u8; 64];
550
+ let b = CgroupsLookupBuilder::new(&mut cbuf, 0, 9);
551
+ let n = b.finish().unwrap();
552
+ let view = CgroupsLookupResponseView::decode(&cbuf[..n]).unwrap();
553
+ assert_eq!(view.item_count, 0);
554
+ assert_eq!(view.generation, 9);
555
+ }
556
+
557
+ #[test]
558
+ fn cgroups_lookup_dispatch_rejects_short_response_buffer() {
559
+ let mut req = [0u8; 64];
560
+ let n = encode_cgroups_lookup_request(&[b"/x"], &mut req).unwrap();
561
+ let mut resp = vec![0u8; CGROUPS_LOOKUP_RESP_HDR_SIZE + LOOKUP_DIR_ENTRY_SIZE - 1];
562
+ assert_eq!(
563
+ dispatch_cgroups_lookup(&req[..n], &mut resp, |_, _| {
564
+ panic!("handler must not run with undersized response buffer")
565
+ })
566
+ .unwrap_err(),
567
+ NipcError::Overflow
568
+ );
569
+ }
570
+
571
+ #[test]
572
+ fn cgroups_lookup_request_rejects_bad_layouts() {
573
+ let mut buf = [0u8; 128];
574
+ let n = encode_cgroups_lookup_request(&[b"/x"], &mut buf).unwrap();
575
+ assert_eq!(
576
+ CgroupsLookupRequestView::decode(&buf[..CGROUPS_LOOKUP_REQ_HDR_SIZE - 1]).unwrap_err(),
577
+ NipcError::Truncated
578
+ );
579
+
580
+ let mut bad = buf[..n].to_vec();
581
+ put_u16(&mut bad, 0, 99);
582
+ assert_eq!(
583
+ CgroupsLookupRequestView::decode(&bad).unwrap_err(),
584
+ NipcError::BadLayout
585
+ );
586
+
587
+ bad.copy_from_slice(&buf[..n]);
588
+ put_u32(&mut bad, 8, 1);
589
+ assert_eq!(
590
+ CgroupsLookupRequestView::decode(&bad).unwrap_err(),
591
+ NipcError::BadLayout
592
+ );
593
+
594
+ bad.copy_from_slice(&buf[..n]);
595
+ put_u32(&mut bad, CGROUPS_LOOKUP_REQ_HDR_SIZE, 1);
596
+ assert_eq!(
597
+ CgroupsLookupRequestView::decode(&bad).unwrap_err(),
598
+ NipcError::BadAlignment
599
+ );
600
+
601
+ bad.copy_from_slice(&buf[..n]);
602
+ put_u32(&mut bad, CGROUPS_LOOKUP_REQ_HDR_SIZE + 4, 4096);
603
+ assert_eq!(
604
+ CgroupsLookupRequestView::decode(&bad).unwrap_err(),
605
+ NipcError::OutOfBounds
606
+ );
607
+
608
+ bad.copy_from_slice(&buf[..n]);
609
+ let last = bad.len() - 1;
610
+ bad[last] = b'x';
611
+ assert_eq!(
612
+ CgroupsLookupRequestView::decode(&bad).unwrap_err(),
613
+ NipcError::MissingNul
614
+ );
615
+
616
+ bad.copy_from_slice(&buf[..n]);
617
+ bad[CGROUPS_LOOKUP_REQ_HDR_SIZE + LOOKUP_DIR_ENTRY_SIZE] = 0;
618
+ assert_eq!(
619
+ CgroupsLookupRequestView::decode(&bad).unwrap_err(),
620
+ NipcError::BadLayout
621
+ );
622
+ }
623
+
624
+ #[test]
625
+ fn cgroups_lookup_response_rejects_bad_layouts() {
626
+ let buf = cgroups_lookup_labeled_response();
627
+ assert_eq!(
628
+ CgroupsLookupResponseView::decode(&buf[..CGROUPS_LOOKUP_RESP_HDR_SIZE - 1])
629
+ .unwrap_err(),
630
+ NipcError::Truncated
631
+ );
632
+
633
+ let mut bad = buf.clone();
634
+ put_u16(&mut bad, 0, 99);
635
+ assert_eq!(
636
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
637
+ NipcError::BadLayout
638
+ );
639
+
640
+ bad = buf.clone();
641
+ put_u16(&mut bad, 2, 1);
642
+ assert_eq!(
643
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
644
+ NipcError::BadLayout
645
+ );
646
+
647
+ bad = buf.clone();
648
+ put_u32(&mut bad, CGROUPS_LOOKUP_RESP_HDR_SIZE, 1);
649
+ assert_eq!(
650
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
651
+ NipcError::BadAlignment
652
+ );
653
+
654
+ bad = buf.clone();
655
+ put_u32(&mut bad, CGROUPS_LOOKUP_RESP_HDR_SIZE + 4, 4096);
656
+ assert_eq!(
657
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
658
+ NipcError::OutOfBounds
659
+ );
660
+
661
+ bad = buf.clone();
662
+ put_u32(
663
+ &mut bad,
664
+ CGROUPS_LOOKUP_RESP_HDR_SIZE + 4,
665
+ (CGROUPS_LOOKUP_ITEM_HDR_SIZE - 1) as u32,
666
+ );
667
+ assert_eq!(
668
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
669
+ NipcError::BadLayout
670
+ );
671
+
672
+ bad = buf.clone();
673
+ let (item_start, _) = response_item_bounds(&bad, CGROUPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
674
+ put_u16(&mut bad, item_start, 99);
675
+ assert_eq!(
676
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
677
+ NipcError::BadLayout
678
+ );
679
+
680
+ bad = buf.clone();
681
+ let (item_start, _) = response_item_bounds(&bad, CGROUPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
682
+ let path_off = u32_at(&bad, item_start + 8) as usize;
683
+ let path_len = u32_at(&bad, item_start + 12) as usize;
684
+ bad[item_start + path_off + path_len] = b'x';
685
+ assert_eq!(
686
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
687
+ NipcError::MissingNul
688
+ );
689
+
690
+ bad = buf.clone();
691
+ let (item_start, _) = response_item_bounds(&bad, CGROUPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
692
+ put_u32(&mut bad, item_start + 8, 4);
693
+ assert_eq!(
694
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
695
+ NipcError::OutOfBounds
696
+ );
697
+
698
+ bad = buf.clone();
699
+ let (item_start, _) = response_item_bounds(&bad, CGROUPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
700
+ let path_off = u32_at(&bad, item_start + 8);
701
+ let path_len = u32_at(&bad, item_start + 12);
702
+ put_u32(&mut bad, item_start + 16, path_off);
703
+ put_u32(&mut bad, item_start + 20, path_len);
704
+ assert_eq!(
705
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
706
+ NipcError::BadLayout
707
+ );
708
+
709
+ bad = buf.clone();
710
+ let (item_start, _) = response_item_bounds(&bad, CGROUPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
711
+ let path_off = u32_at(&bad, item_start + 8) as usize;
712
+ let path_len = u32_at(&bad, item_start + 12) as usize;
713
+ let name_off = u32_at(&bad, item_start + 16) as usize;
714
+ let name_len = u32_at(&bad, item_start + 20) as usize;
715
+ let fixed_end = (path_off + path_len + 1).max(name_off + name_len + 1);
716
+ bad[item_start + fixed_end] = 1;
717
+ assert_eq!(
718
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
719
+ NipcError::BadLayout
720
+ );
721
+
722
+ bad = buf.clone();
723
+ let (item_start, _) = response_item_bounds(&bad, CGROUPS_LOOKUP_RESP_HDR_SIZE, 1, 0);
724
+ let path_off = u32_at(&bad, item_start + 8) as usize;
725
+ let path_len = u32_at(&bad, item_start + 12) as usize;
726
+ let name_off = u32_at(&bad, item_start + 16) as usize;
727
+ let name_len = u32_at(&bad, item_start + 20) as usize;
728
+ let fixed_end = (path_off + path_len + 1).max(name_off + name_len + 1);
729
+ let table_start = align8(fixed_end);
730
+ put_u32(&mut bad, item_start + table_start + 4, 0);
731
+ assert_eq!(
732
+ CgroupsLookupResponseView::decode(&bad).unwrap_err(),
733
+ NipcError::BadLayout
734
+ );
735
+
736
+ let mut two = vec![0u8; 512];
737
+ let mut b = CgroupsLookupBuilder::new(&mut two, 2, 1);
738
+ b.add(CGROUP_LOOKUP_UNKNOWN_PERMANENT, 0, b"/a", b"", &[])
739
+ .unwrap();
740
+ b.add(CGROUP_LOOKUP_UNKNOWN_PERMANENT, 0, b"/b", b"", &[])
741
+ .unwrap();
742
+ let n = b.finish().unwrap();
743
+ two.truncate(n);
744
+ let first_off = u32_at(&two, CGROUPS_LOOKUP_RESP_HDR_SIZE);
745
+ put_u32(
746
+ &mut two,
747
+ CGROUPS_LOOKUP_RESP_HDR_SIZE + LOOKUP_DIR_ENTRY_SIZE,
748
+ first_off,
749
+ );
750
+ assert_eq!(
751
+ CgroupsLookupResponseView::decode(&two).unwrap_err(),
752
+ NipcError::BadLayout
753
+ );
754
+ }
755
+}
src/crates/netipc/src/protocol/lookup/common.rs
new
+390
@@ -0,0 +1,390 @@
1
+//! Shared helpers for lookup codecs.
2
+
3
+use crate::protocol::{align8, NipcError, StrView, ALIGNMENT};
4
+
5
+pub const LOOKUP_DIR_ENTRY_SIZE: usize = 8;
6
+pub const LOOKUP_LABEL_ENTRY_SIZE: usize = 16;
7
+
8
+pub const ORCHESTRATOR_UNKNOWN: u16 = 0;
9
+pub const ORCHESTRATOR_SYSTEMD: u16 = 1;
10
+pub const ORCHESTRATOR_DOCKER: u16 = 2;
11
+pub const ORCHESTRATOR_K8S: u16 = 3;
12
+pub const ORCHESTRATOR_KVM: u16 = 4;
13
+pub const ORCHESTRATOR_LXC: u16 = 5;
14
+pub const ORCHESTRATOR_PODMAN: u16 = 6;
15
+pub const ORCHESTRATOR_NSPAWN: u16 = 7;
16
+
17
+#[derive(Debug, Clone, Copy, PartialEq)]
18
+pub struct LookupLabelView<'a> {
19
+ pub key: StrView<'a>,
20
+ pub value: StrView<'a>,
21
+}
22
+
23
+#[inline]
24
+pub(super) fn u16_at(buf: &[u8], off: usize) -> u16 {
25
+ u16::from_ne_bytes(buf[off..off + 2].try_into().unwrap())
26
+}
27
+
28
+#[inline]
29
+pub(super) fn u32_at(buf: &[u8], off: usize) -> u32 {
30
+ u32::from_ne_bytes(buf[off..off + 4].try_into().unwrap())
31
+}
32
+
33
+#[inline]
34
+pub(super) fn u64_at(buf: &[u8], off: usize) -> u64 {
35
+ u64::from_ne_bytes(buf[off..off + 8].try_into().unwrap())
36
+}
37
+
38
+#[inline]
39
+pub(super) fn put_u16(buf: &mut [u8], off: usize, value: u16) {
40
+ buf[off..off + 2].copy_from_slice(&value.to_ne_bytes());
41
+}
42
+
43
+#[inline]
44
+pub(super) fn put_u32(buf: &mut [u8], off: usize, value: u32) {
45
+ buf[off..off + 4].copy_from_slice(&value.to_ne_bytes());
46
+}
47
+
48
+#[inline]
49
+pub(super) fn put_u64(buf: &mut [u8], off: usize, value: u64) {
50
+ buf[off..off + 8].copy_from_slice(&value.to_ne_bytes());
51
+}
52
+
53
+pub(super) fn checked_u32(value: usize) -> Result<u32, NipcError> {
54
+ u32::try_from(value).map_err(|_| NipcError::Overflow)
55
+}
56
+
57
+pub(super) fn checked_u16(value: usize) -> Result<u16, NipcError> {
58
+ u16::try_from(value).map_err(|_| NipcError::Overflow)
59
+}
60
+
61
+pub(super) fn source_string_invalid(bytes: &[u8], require_non_empty: bool) -> bool {
62
+ (require_non_empty && bytes.is_empty()) || bytes.contains(&0)
63
+}
64
+
65
+pub(super) fn validate_lookup_dir(
66
+ buf: &[u8],
67
+ dir_start: usize,
68
+ item_count: u32,
69
+ packed_area_len: usize,
70
+ min_len: usize,
71
+ exact_len: Option<usize>,
72
+) -> Result<(), NipcError> {
73
+ let dir_size = (item_count as usize)
74
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
75
+ .ok_or(NipcError::BadItemCount)?;
76
+ if dir_start
77
+ .checked_add(dir_size)
78
+ .ok_or(NipcError::BadItemCount)?
79
+ > buf.len()
80
+ {
81
+ return Err(NipcError::Truncated);
82
+ }
83
+
84
+ let mut prev_end = 0usize;
85
+ for i in 0..item_count as usize {
86
+ let base = dir_start + i * LOOKUP_DIR_ENTRY_SIZE;
87
+ let off = u32_at(buf, base) as usize;
88
+ let len = u32_at(buf, base + 4) as usize;
89
+ if off % ALIGNMENT != 0 {
90
+ return Err(NipcError::BadAlignment);
91
+ }
92
+ if let Some(exact) = exact_len {
93
+ if len != exact {
94
+ return Err(NipcError::BadLayout);
95
+ }
96
+ } else if len < min_len {
97
+ return Err(NipcError::BadLayout);
98
+ }
99
+ let end = off.checked_add(len).ok_or(NipcError::OutOfBounds)?;
100
+ if end > packed_area_len {
101
+ return Err(NipcError::OutOfBounds);
102
+ }
103
+ if i > 0 && off < prev_end {
104
+ return Err(NipcError::BadLayout);
105
+ }
106
+ prev_end = end;
107
+ }
108
+ Ok(())
109
+}
110
+
111
+pub(super) fn lookup_string<'a>(
112
+ item: &'a [u8],
113
+ hdr_size: usize,
114
+ offset: usize,
115
+ length: usize,
116
+) -> Result<(StrView<'a>, usize), NipcError> {
117
+ if offset < hdr_size {
118
+ return Err(NipcError::OutOfBounds);
119
+ }
120
+ let nul = offset.checked_add(length).ok_or(NipcError::OutOfBounds)?;
121
+ if nul >= item.len() {
122
+ return Err(NipcError::OutOfBounds);
123
+ }
124
+ if item[nul] != 0 {
125
+ return Err(NipcError::MissingNul);
126
+ }
127
+ if item[offset..nul].contains(&0) {
128
+ return Err(NipcError::BadLayout);
129
+ }
130
+ Ok((
131
+ StrView {
132
+ bytes: &item[offset..nul + 1],
133
+ len: length as u32,
134
+ },
135
+ nul + 1,
136
+ ))
137
+}
138
+
139
+#[inline]
140
+pub(super) fn overlap(a_start: usize, a_end: usize, b_start: usize, b_end: usize) -> bool {
141
+ a_start < b_end && b_start < a_end
142
+}
143
+
144
+pub(super) fn checked_subslice<'a>(
145
+ buf: &'a [u8],
146
+ base: usize,
147
+ offset: usize,
148
+ len: usize,
149
+) -> Result<&'a [u8], NipcError> {
150
+ let start = base.checked_add(offset).ok_or(NipcError::OutOfBounds)?;
151
+ let end = start.checked_add(len).ok_or(NipcError::OutOfBounds)?;
152
+ buf.get(start..end).ok_or(NipcError::OutOfBounds)
153
+}
154
+
155
+pub(super) fn lookup_data_offset(hdr_size: usize, item_count: u32) -> Result<usize, NipcError> {
156
+ (item_count as usize)
157
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
158
+ .and_then(|v| hdr_size.checked_add(v))
159
+ .ok_or(NipcError::BadItemCount)
160
+}
161
+
162
+pub(super) fn lookup_dir_entry_offset(hdr_size: usize, index: u32) -> Result<usize, NipcError> {
163
+ (index as usize)
164
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
165
+ .and_then(|v| hdr_size.checked_add(v))
166
+ .ok_or(NipcError::BadItemCount)
167
+}
168
+
169
+pub(super) fn validate_labels(
170
+ item: &[u8],
171
+ hdr_size: usize,
172
+ label_count: u16,
173
+ fixed_end: usize,
174
+) -> Result<usize, NipcError> {
175
+ if label_count == 0 {
176
+ if fixed_end != item.len() {
177
+ return Err(NipcError::BadLayout);
178
+ }
179
+ return Ok(fixed_end);
180
+ }
181
+
182
+ let table_start = align8(fixed_end);
183
+ if table_start > item.len() {
184
+ return Err(NipcError::OutOfBounds);
185
+ }
186
+ if item[fixed_end..table_start].iter().any(|&b| b != 0) {
187
+ return Err(NipcError::BadLayout);
188
+ }
189
+
190
+ let table_bytes = (label_count as usize)
191
+ .checked_mul(LOOKUP_LABEL_ENTRY_SIZE)
192
+ .ok_or(NipcError::OutOfBounds)?;
193
+ let mut expected = table_start
194
+ .checked_add(table_bytes)
195
+ .ok_or(NipcError::OutOfBounds)?;
196
+ if expected > item.len() {
197
+ return Err(NipcError::OutOfBounds);
198
+ }
199
+
200
+ for i in 0..label_count as usize {
201
+ let entry_rel = i
202
+ .checked_mul(LOOKUP_LABEL_ENTRY_SIZE)
203
+ .ok_or(NipcError::OutOfBounds)?;
204
+ let base = table_start
205
+ .checked_add(entry_rel)
206
+ .ok_or(NipcError::OutOfBounds)?;
207
+ let key_off = u32_at(item, base) as usize;
208
+ let key_len = u32_at(item, base + 4) as usize;
209
+ let value_off = u32_at(item, base + 8) as usize;
210
+ let value_len = u32_at(item, base + 12) as usize;
211
+ if key_len == 0 || key_off != expected {
212
+ return Err(NipcError::BadLayout);
213
+ }
214
+ let (_, key_end) = lookup_string(item, hdr_size, key_off, key_len)?;
215
+ expected = key_end;
216
+ if value_off != expected {
217
+ return Err(NipcError::BadLayout);
218
+ }
219
+ let (_, value_end) = lookup_string(item, hdr_size, value_off, value_len)?;
220
+ expected = value_end;
221
+ }
222
+
223
+ if expected != item.len() {
224
+ return Err(NipcError::BadLayout);
225
+ }
226
+ Ok(table_start)
227
+}
228
+
229
+pub(super) fn label_at<'a>(
230
+ item: &'a [u8],
231
+ hdr_size: usize,
232
+ label_count: u16,
233
+ label_table_offset: usize,
234
+ index: u32,
235
+) -> Result<LookupLabelView<'a>, NipcError> {
236
+ if index >= label_count as u32 {
237
+ return Err(NipcError::OutOfBounds);
238
+ }
239
+ let entry_offset = (index as usize)
240
+ .checked_mul(LOOKUP_LABEL_ENTRY_SIZE)
241
+ .ok_or(NipcError::OutOfBounds)?;
242
+ let base = label_table_offset
243
+ .checked_add(entry_offset)
244
+ .ok_or(NipcError::OutOfBounds)?;
245
+ let entry_end = base
246
+ .checked_add(LOOKUP_LABEL_ENTRY_SIZE)
247
+ .ok_or(NipcError::OutOfBounds)?;
248
+ if entry_end > item.len() {
249
+ return Err(NipcError::OutOfBounds);
250
+ }
251
+ let key_off = u32_at(item, base) as usize;
252
+ let key_len = u32_at(item, base + 4) as usize;
253
+ let value_off = u32_at(item, base + 8) as usize;
254
+ let value_len = u32_at(item, base + 12) as usize;
255
+ let (key, _) = lookup_string(item, hdr_size, key_off, key_len)?;
256
+ let (value, _) = lookup_string(item, hdr_size, value_off, value_len)?;
257
+ Ok(LookupLabelView { key, value })
258
+}
259
+
260
+pub(super) fn write_lookup_labels(
261
+ item: &mut [u8],
262
+ table_start: usize,
263
+ table_bytes: usize,
264
+ labels: &[(&[u8], &[u8])],
265
+) -> Result<usize, NipcError> {
266
+ let mut next = table_start
267
+ .checked_add(table_bytes)
268
+ .ok_or(NipcError::Overflow)?;
269
+ for (i, (key, value)) in labels.iter().enumerate() {
270
+ let entry_offset = i
271
+ .checked_mul(LOOKUP_LABEL_ENTRY_SIZE)
272
+ .ok_or(NipcError::Overflow)?;
273
+ let entry = table_start
274
+ .checked_add(entry_offset)
275
+ .ok_or(NipcError::Overflow)?;
276
+ let value_offset = next
277
+ .checked_add(key.len())
278
+ .and_then(|v| v.checked_add(1))
279
+ .ok_or(NipcError::Overflow)?;
280
+ put_u32(item, entry, checked_u32(next)?);
281
+ put_u32(item, entry + 4, checked_u32(key.len())?);
282
+ put_u32(item, entry + 8, checked_u32(value_offset)?);
283
+ put_u32(item, entry + 12, checked_u32(value.len())?);
284
+ item[next..next + key.len()].copy_from_slice(key);
285
+ item[next + key.len()] = 0;
286
+ next = value_offset;
287
+ item[next..next + value.len()].copy_from_slice(value);
288
+ item[next + value.len()] = 0;
289
+ next = next
290
+ .checked_add(value.len())
291
+ .and_then(|v| v.checked_add(1))
292
+ .ok_or(NipcError::Overflow)?;
293
+ }
294
+ Ok(next)
295
+}
296
+
297
+pub(super) fn label_layout(
298
+ fixed_end: usize,
299
+ labels: &[(&[u8], &[u8])],
300
+) -> Result<(usize, usize, usize), NipcError> {
301
+ if labels.is_empty() {
302
+ return Ok((fixed_end, 0, fixed_end));
303
+ }
304
+ let table_start = align8(fixed_end);
305
+ let table_bytes = labels
306
+ .len()
307
+ .checked_mul(LOOKUP_LABEL_ENTRY_SIZE)
308
+ .ok_or(NipcError::Overflow)?;
309
+ let mut item_size = table_start
310
+ .checked_add(table_bytes)
311
+ .ok_or(NipcError::Overflow)?;
312
+ for (key, value) in labels {
313
+ if source_string_invalid(key, true) || source_string_invalid(value, false) {
314
+ return Err(NipcError::BadLayout);
315
+ }
316
+ let key_size = key.len().checked_add(1).ok_or(NipcError::Overflow)?;
317
+ let value_size = value.len().checked_add(1).ok_or(NipcError::Overflow)?;
318
+ item_size = item_size
319
+ .checked_add(key_size)
320
+ .and_then(|v| v.checked_add(value_size))
321
+ .ok_or(NipcError::Overflow)?;
322
+ }
323
+ Ok((table_start, table_bytes, item_size))
324
+}
325
+
326
+pub(super) fn finish_lookup_response(
327
+ buf: &mut [u8],
328
+ hdr_size: usize,
329
+ item_count: u32,
330
+ data_offset: usize,
331
+ generation: u64,
332
+) -> Result<usize, NipcError> {
333
+ put_u16(buf, 0, 1);
334
+ put_u16(buf, 2, 0);
335
+ put_u32(buf, 4, item_count);
336
+ put_u64(buf, 8, generation);
337
+ if item_count == 0 {
338
+ return Ok(hdr_size);
339
+ }
340
+ let final_packed_start = (item_count as usize)
341
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
342
+ .and_then(|v| hdr_size.checked_add(v))
343
+ .ok_or(NipcError::Overflow)?;
344
+ let first_item_abs = u32_at(buf, hdr_size) as usize;
345
+ let packed_data_len = data_offset
346
+ .checked_sub(first_item_abs)
347
+ .ok_or(NipcError::Overflow)?;
348
+ if final_packed_start < first_item_abs {
349
+ let copy_end = first_item_abs
350
+ .checked_add(packed_data_len)
351
+ .ok_or(NipcError::Overflow)?;
352
+ if copy_end > buf.len() {
353
+ return Err(NipcError::Overflow);
354
+ }
355
+ let dest_end = final_packed_start
356
+ .checked_add(packed_data_len)
357
+ .ok_or(NipcError::Overflow)?;
358
+ if dest_end > buf.len() {
359
+ return Err(NipcError::Overflow);
360
+ }
361
+ buf.copy_within(first_item_abs..copy_end, final_packed_start);
362
+ }
363
+ for i in 0..item_count as usize {
364
+ let entry_offset = i
365
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
366
+ .ok_or(NipcError::Overflow)?;
367
+ let entry = hdr_size
368
+ .checked_add(entry_offset)
369
+ .ok_or(NipcError::Overflow)?;
370
+ let abs = u32_at(buf, entry) as usize;
371
+ let rel = abs.checked_sub(first_item_abs).ok_or(NipcError::Overflow)?;
372
+ put_u32(buf, entry, checked_u32(rel)?);
373
+ }
374
+ final_packed_start
375
+ .checked_add(packed_data_len)
376
+ .ok_or(NipcError::Overflow)
377
+}
378
+
379
+#[cfg(test)]
380
+pub(super) fn response_item_bounds(
381
+ buf: &[u8],
382
+ hdr_size: usize,
383
+ item_count: usize,
384
+ index: usize,
385
+) -> (usize, usize) {
386
+ let dir = hdr_size + index * LOOKUP_DIR_ENTRY_SIZE;
387
+ let off = u32_at(buf, dir) as usize;
388
+ let len = u32_at(buf, dir + 4) as usize;
389
+ (hdr_size + item_count * LOOKUP_DIR_ENTRY_SIZE + off, len)
390
+}
src/crates/netipc/src/protocol/mod.rs
+7
-1778
@@ -6,13 +6,15 @@
6
//! Decoded `View` types borrow the underlying buffer and are valid only while
7
//! that buffer lives. Copy immediately if the data is needed later.
8
9
-mod cgroups;
9
+mod cgroups_snapshot;
10
mod increment;
11
+mod lookup;
12
mod string_reverse;
13
14
// Re-export all public symbols from submodules.
14
-pub use cgroups::*;
15
+pub use cgroups_snapshot::*;
16
pub use increment::*;
17
+pub use lookup::*;
18
pub use string_reverse::*;
19
20
// ---------------------------------------------------------------------------
@@ -50,6 +52,8 @@ pub const CODE_HELLO_ACK: u16 = 2;
52
pub const METHOD_INCREMENT: u16 = 1;
53
pub const METHOD_CGROUPS_SNAPSHOT: u16 = 2;
54
pub const METHOD_STRING_REVERSE: u16 = 3;
55
+pub const METHOD_CGROUPS_LOOKUP: u16 = 4;
56
+pub const METHOD_APPS_LOOKUP: u16 = 5;
57
58
// Profile bits
59
pub const PROFILE_BASELINE: u32 = 0x01;
@@ -616,1780 +620,5 @@ impl HelloAck {
620
}
621
}
622
619
-// ===========================================================================
620
-// Tests
621
-// ===========================================================================
622
-
623
#[cfg(test)]
624
-mod tests {
625
- use super::*;
626
-
627
- // -----------------------------------------------------------------------
628
- // Outer message header tests
629
- // -----------------------------------------------------------------------
630
-
631
- #[test]
632
- fn header_roundtrip() {
633
- let h = Header {
634
- magic: MAGIC_MSG,
635
- version: VERSION,
636
- header_len: HEADER_LEN,
637
- kind: KIND_REQUEST,
638
- flags: FLAG_BATCH,
639
- code: METHOD_CGROUPS_SNAPSHOT,
640
- transport_status: STATUS_OK,
641
- payload_len: 12345,
642
- item_count: 42,
643
- message_id: 0xDEAD_BEEF_CAFE_BABE,
644
- };
645
-
646
- let mut buf = [0u8; 64];
647
- let n = h.encode(&mut buf);
648
- assert_eq!(n, 32);
649
-
650
- let out = Header::decode(&buf[..n]).unwrap();
651
- assert_eq!(out, h);
652
- }
653
-
654
- #[test]
655
- fn header_encode_too_small() {
656
- let h = Header::default();
657
- let mut buf = [0u8; 16];
658
- assert_eq!(h.encode(&mut buf), 0);
659
- }
660
-
661
- #[test]
662
- fn header_decode_truncated() {
663
- let buf = [0u8; 31];
664
- assert_eq!(Header::decode(&buf), Err(NipcError::Truncated));
665
- }
666
-
667
- #[test]
668
- fn header_decode_bad_magic() {
669
- let h = Header {
670
- magic: 0x12345678,
671
- version: VERSION,
672
- header_len: HEADER_LEN,
673
- kind: KIND_REQUEST,
674
- ..Default::default()
675
- };
676
- let mut buf = [0u8; 32];
677
- h.encode(&mut buf);
678
- assert_eq!(Header::decode(&buf), Err(NipcError::BadMagic));
679
- }
680
-
681
- #[test]
682
- fn header_decode_bad_version() {
683
- let h = Header {
684
- magic: MAGIC_MSG,
685
- version: 99,
686
- header_len: HEADER_LEN,
687
- kind: KIND_REQUEST,
688
- ..Default::default()
689
- };
690
- let mut buf = [0u8; 32];
691
- h.encode(&mut buf);
692
- assert_eq!(Header::decode(&buf), Err(NipcError::BadVersion));
693
- }
694
-
695
- #[test]
696
- fn header_decode_bad_header_len() {
697
- let h = Header {
698
- magic: MAGIC_MSG,
699
- version: VERSION,
700
- header_len: 64,
701
- kind: KIND_REQUEST,
702
- ..Default::default()
703
- };
704
- let mut buf = [0u8; 32];
705
- h.encode(&mut buf);
706
- assert_eq!(Header::decode(&buf), Err(NipcError::BadHeaderLen));
707
- }
708
-
709
- #[test]
710
- fn header_decode_bad_kind() {
711
- // kind = 0
712
- let h = Header {
713
- magic: MAGIC_MSG,
714
- version: VERSION,
715
- header_len: HEADER_LEN,
716
- kind: 0,
717
- ..Default::default()
718
- };
719
- let mut buf = [0u8; 32];
720
- h.encode(&mut buf);
721
- assert_eq!(Header::decode(&buf), Err(NipcError::BadKind));
722
-
723
- // kind = 4
724
- let h2 = Header { kind: 4, ..h };
725
- h2.encode(&mut buf);
726
- assert_eq!(Header::decode(&buf), Err(NipcError::BadKind));
727
- }
728
-
729
- #[test]
730
- fn header_all_kinds() {
731
- for k in KIND_REQUEST..=KIND_CONTROL {
732
- let h = Header {
733
- magic: MAGIC_MSG,
734
- version: VERSION,
735
- header_len: HEADER_LEN,
736
- kind: k,
737
- ..Default::default()
738
- };
739
- let mut buf = [0u8; 32];
740
- h.encode(&mut buf);
741
- let out = Header::decode(&buf).unwrap();
742
- assert_eq!(out.kind, k);
743
- }
744
- }
745
-
746
- #[test]
747
- fn header_wire_bytes() {
748
- let h = Header {
749
- magic: MAGIC_MSG,
750
- version: VERSION,
751
- header_len: HEADER_LEN,
752
- kind: KIND_REQUEST,
753
- flags: 0,
754
- code: METHOD_CGROUPS_SNAPSHOT,
755
- transport_status: STATUS_OK,
756
- payload_len: 4,
757
- item_count: 1,
758
- message_id: 1,
759
- };
760
-
761
- let mut buf = [0u8; 32];
762
- h.encode(&mut buf);
763
-
764
- // magic = 0x4e495043 LE: 43 50 49 4e
765
- assert_eq!(&buf[0..4], &[0x43, 0x50, 0x49, 0x4e]);
766
- // version = 1 LE: 01 00
767
- assert_eq!(&buf[4..6], &[0x01, 0x00]);
768
- // header_len = 32 LE: 20 00
769
- assert_eq!(&buf[6..8], &[0x20, 0x00]);
770
- // kind = 1 LE: 01 00
771
- assert_eq!(&buf[8..10], &[0x01, 0x00]);
772
- // code = 2 LE: 02 00
773
- assert_eq!(&buf[12..14], &[0x02, 0x00]);
774
- }
775
-
776
- // -----------------------------------------------------------------------
777
- // Chunk continuation header tests
778
- // -----------------------------------------------------------------------
779
-
780
- #[test]
781
- fn chunk_header_roundtrip() {
782
- let c = ChunkHeader {
783
- magic: MAGIC_CHUNK,
784
- version: VERSION,
785
- flags: 0,
786
- message_id: 0x1234_5678_90AB_CDEF,
787
- total_message_len: 100000,
788
- chunk_index: 3,
789
- chunk_count: 10,
790
- chunk_payload_len: 8192,
791
- };
792
-
793
- let mut buf = [0u8; 64];
794
- let n = c.encode(&mut buf);
795
- assert_eq!(n, 32);
796
-
797
- let out = ChunkHeader::decode(&buf[..n]).unwrap();
798
- assert_eq!(out, c);
799
- }
800
-
801
- #[test]
802
- fn chunk_decode_truncated() {
803
- let buf = [0u8; 31];
804
- assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::Truncated));
805
- }
806
-
807
- #[test]
808
- fn chunk_decode_bad_magic() {
809
- let c = ChunkHeader {
810
- magic: MAGIC_MSG, // wrong magic for chunk
811
- version: VERSION,
812
- ..Default::default()
813
- };
814
- let mut buf = [0u8; 32];
815
- c.encode(&mut buf);
816
- assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::BadMagic));
817
- }
818
-
819
- #[test]
820
- fn chunk_decode_bad_version() {
821
- let c = ChunkHeader {
822
- magic: MAGIC_CHUNK,
823
- version: 2,
824
- ..Default::default()
825
- };
826
- let mut buf = [0u8; 32];
827
- c.encode(&mut buf);
828
- assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::BadVersion));
829
- }
830
-
831
- #[test]
832
- fn chunk_encode_too_small() {
833
- let c = ChunkHeader::default();
834
- let mut buf = [0u8; 16];
835
- assert_eq!(c.encode(&mut buf), 0);
836
- }
837
-
838
- #[test]
839
- fn chunk_wire_bytes() {
840
- let c = ChunkHeader {
841
- magic: MAGIC_CHUNK,
842
- version: VERSION,
843
- flags: 0,
844
- message_id: 1,
845
- total_message_len: 256,
846
- chunk_index: 1,
847
- chunk_count: 3,
848
- chunk_payload_len: 100,
849
- };
850
-
851
- let mut buf = [0u8; 32];
852
- c.encode(&mut buf);
853
-
854
- // magic = 0x4e43484b LE: 4b 48 43 4e
855
- assert_eq!(&buf[0..4], &[0x4b, 0x48, 0x43, 0x4e]);
856
- }
857
-
858
- // -----------------------------------------------------------------------
859
- // Batch item directory tests
860
- // -----------------------------------------------------------------------
861
-
862
- #[test]
863
- fn batch_dir_roundtrip() {
864
- let entries = [
865
- BatchEntry {
866
- offset: 0,
867
- length: 100,
868
- },
869
- BatchEntry {
870
- offset: 104,
871
- length: 200,
872
- },
873
- BatchEntry {
874
- offset: 304,
875
- length: 50,
876
- },
877
- ];
878
-
879
- let mut buf = [0u8; 64];
880
- let n = batch_dir_encode(&entries, &mut buf);
881
- assert_eq!(n, 24);
882
-
883
- let out = batch_dir_decode(&buf[..n], 3, 400).unwrap();
884
- assert_eq!(out[0], entries[0]);
885
- assert_eq!(out[1], entries[1]);
886
- assert_eq!(out[2], entries[2]);
887
- }
888
-
889
- #[test]
890
- fn batch_dir_decode_truncated() {
891
- let buf = [0u8; 12];
892
- assert_eq!(batch_dir_decode(&buf, 2, 1000), Err(NipcError::Truncated));
893
- }
894
-
895
- #[test]
896
- fn batch_dir_decode_oob() {
897
- let e = BatchEntry {
898
- offset: 0,
899
- length: 200,
900
- };
901
- let mut buf = [0u8; 8];
902
- batch_dir_encode(&[e], &mut buf);
903
- assert_eq!(batch_dir_decode(&buf, 1, 100), Err(NipcError::OutOfBounds));
904
- }
905
-
906
- #[test]
907
- fn batch_dir_decode_bad_alignment() {
908
- let mut buf = [0u8; 8];
909
- // Manually write unaligned offset
910
- buf[0..4].copy_from_slice(&3u32.to_ne_bytes());
911
- buf[4..8].copy_from_slice(&10u32.to_ne_bytes());
912
- assert_eq!(batch_dir_decode(&buf, 1, 100), Err(NipcError::BadAlignment));
913
- }
914
-
915
- // -----------------------------------------------------------------------
916
- // Batch builder + extraction tests
917
- // -----------------------------------------------------------------------
918
-
919
- #[test]
920
- fn batch_builder_roundtrip() {
921
- let mut buf = [0u8; 1024];
922
- let mut b = BatchBuilder::new(&mut buf, 4);
923
-
924
- let item1 = [1u8, 2, 3, 4, 5];
925
- let item2 = [10u8, 20, 30];
926
- let item3 = [0xAAu8, 0xBB];
927
-
928
- b.add(&item1).unwrap();
929
- b.add(&item2).unwrap();
930
- b.add(&item3).unwrap();
931
-
932
- let (total, count) = b.finish();
933
- assert_eq!(count, 3);
934
- assert!(total > 0);
935
-
936
- // Extract items
937
- let (data, len) = batch_item_get(&buf[..total], 3, 0).unwrap();
938
- assert_eq!(len as usize, item1.len());
939
- assert_eq!(data, &item1);
940
-
941
- let (data, len) = batch_item_get(&buf[..total], 3, 1).unwrap();
942
- assert_eq!(len as usize, item2.len());
943
- assert_eq!(data, &item2);
944
-
945
- let (data, len) = batch_item_get(&buf[..total], 3, 2).unwrap();
946
- assert_eq!(len as usize, item3.len());
947
- assert_eq!(data, &item3);
948
- }
949
-
950
- #[test]
951
- fn batch_builder_overflow() {
952
- let mut buf = [0u8; 32];
953
- let mut b = BatchBuilder::new(&mut buf, 1);
954
- let item = [1u8];
955
- b.add(&item).unwrap();
956
- assert_eq!(b.add(&item), Err(NipcError::Overflow));
957
- }
958
-
959
- #[test]
960
- fn batch_builder_buf_overflow() {
961
- let mut buf = [0u8; 24];
962
- let mut b = BatchBuilder::new(&mut buf, 1);
963
- let big = [0u8; 100];
964
- assert_eq!(b.add(&big), Err(NipcError::Overflow));
965
- }
966
-
967
- #[test]
968
- fn batch_item_get_oob_index() {
969
- let mut buf = [0u8; 64];
970
- let mut b = BatchBuilder::new(&mut buf, 2);
971
- b.add(&[1u8]).unwrap();
972
- let (total, count) = b.finish();
973
- assert_eq!(
974
- batch_item_get(&buf[..total], count, 5),
975
- Err(NipcError::OutOfBounds)
976
- );
977
- }
978
-
979
- #[test]
980
- fn batch_empty() {
981
- let mut buf = [0u8; 64];
982
- let b = BatchBuilder::new(&mut buf, 4);
983
- let (total, count) = b.finish();
984
- assert_eq!(count, 0);
985
- assert_eq!(total, 0);
986
- }
987
-
988
- // -----------------------------------------------------------------------
989
- // Hello payload tests
990
- // -----------------------------------------------------------------------
991
-
992
- #[test]
993
- fn hello_roundtrip() {
994
- let h = Hello {
995
- layout_version: 1,
996
- flags: 0,
997
- supported_profiles: PROFILE_BASELINE | PROFILE_SHM_FUTEX,
998
- preferred_profiles: PROFILE_SHM_FUTEX,
999
- max_request_payload_bytes: 4096,
1000
- max_request_batch_items: 100,
1001
- max_response_payload_bytes: 1048576,
1002
- max_response_batch_items: 1,
1003
- auth_token: 0xAABB_CCDD_EEFF_0011,
1004
- packet_size: 65536,
1005
- };
1006
-
1007
- let mut buf = [0u8; 64];
1008
- let n = h.encode(&mut buf);
1009
- assert_eq!(n, 44);
1010
-
1011
- let out = Hello::decode(&buf[..n]).unwrap();
1012
- assert_eq!(out, h);
1013
- }
1014
-
1015
- #[test]
1016
- fn hello_decode_truncated() {
1017
- let buf = [0u8; 43];
1018
- assert_eq!(Hello::decode(&buf), Err(NipcError::Truncated));
1019
- }
1020
-
1021
- #[test]
1022
- fn hello_decode_bad_layout() {
1023
- let h = Hello {
1024
- layout_version: 99,
1025
- ..Default::default()
1026
- };
1027
- let mut buf = [0u8; 44];
1028
- h.encode(&mut buf);
1029
- assert_eq!(Hello::decode(&buf), Err(NipcError::BadLayout));
1030
- }
1031
-
1032
- #[test]
1033
- fn hello_encode_too_small() {
1034
- let h = Hello::default();
1035
- let mut buf = [0u8; 10];
1036
- assert_eq!(h.encode(&mut buf), 0);
1037
- }
1038
-
1039
- // -----------------------------------------------------------------------
1040
- // Hello-ack payload tests
1041
- // -----------------------------------------------------------------------
1042
-
1043
- #[test]
1044
- fn hello_ack_roundtrip() {
1045
- let h = HelloAck {
1046
- layout_version: 1,
1047
- flags: 0,
1048
- server_supported_profiles: 0x07,
1049
- intersection_profiles: 0x05,
1050
- selected_profile: PROFILE_SHM_FUTEX,
1051
- agreed_max_request_payload_bytes: 2048,
1052
- agreed_max_request_batch_items: 50,
1053
- agreed_max_response_payload_bytes: 65536,
1054
- agreed_max_response_batch_items: 1,
1055
- agreed_packet_size: 32768,
1056
- session_id: 42,
1057
- };
1058
-
1059
- let mut buf = [0u8; 64];
1060
- let n = h.encode(&mut buf);
1061
- assert_eq!(n, 48);
1062
-
1063
- let out = HelloAck::decode(&buf[..n]).unwrap();
1064
- assert_eq!(out, h);
1065
- }
1066
-
1067
- #[test]
1068
- fn hello_ack_decode_truncated() {
1069
- let buf = [0u8; 47];
1070
- assert_eq!(HelloAck::decode(&buf), Err(NipcError::Truncated));
1071
- }
1072
-
1073
- #[test]
1074
- fn hello_ack_decode_bad_layout() {
1075
- let h = HelloAck {
1076
- layout_version: 0,
1077
- ..Default::default()
1078
- };
1079
- let mut buf = [0u8; 48];
1080
- h.encode(&mut buf);
1081
- assert_eq!(HelloAck::decode(&buf), Err(NipcError::BadLayout));
1082
- }
1083
-
1084
- #[test]
1085
- fn hello_ack_encode_too_small() {
1086
- let h = HelloAck::default();
1087
- let mut buf = [0u8; 10];
1088
- assert_eq!(h.encode(&mut buf), 0);
1089
- }
1090
-
1091
- // -----------------------------------------------------------------------
1092
- // Cgroups snapshot request tests
1093
- // -----------------------------------------------------------------------
1094
-
1095
- #[test]
1096
- fn cgroups_req_roundtrip() {
1097
- let r = CgroupsRequest {
1098
- layout_version: 1,
1099
- flags: 0,
1100
- };
1101
-
1102
- let mut buf = [0u8; 16];
1103
- let n = r.encode(&mut buf);
1104
- assert_eq!(n, 4);
1105
-
1106
- let out = CgroupsRequest::decode(&buf[..n]).unwrap();
1107
- assert_eq!(out, r);
1108
- }
1109
-
1110
- #[test]
1111
- fn cgroups_req_decode_truncated() {
1112
- let buf = [0u8; 3];
1113
- assert_eq!(CgroupsRequest::decode(&buf), Err(NipcError::Truncated));
1114
- }
1115
-
1116
- #[test]
1117
- fn cgroups_req_decode_bad_layout() {
1118
- let r = CgroupsRequest {
1119
- layout_version: 5,
1120
- flags: 0,
1121
- };
1122
- let mut buf = [0u8; 4];
1123
- r.encode(&mut buf);
1124
- assert_eq!(CgroupsRequest::decode(&buf), Err(NipcError::BadLayout));
1125
- }
1126
-
1127
- #[test]
1128
- fn cgroups_req_encode_too_small() {
1129
- let r = CgroupsRequest::default();
1130
- let mut buf = [0u8; 2];
1131
- assert_eq!(r.encode(&mut buf), 0);
1132
- }
1133
-
1134
- // -----------------------------------------------------------------------
1135
- // Cgroups snapshot response tests
1136
- // -----------------------------------------------------------------------
1137
-
1138
- // Private constants needed by tests -- mirror the values from cgroups.rs
1139
- const CGROUPS_RESP_HDR_SIZE: usize = 24;
1140
- const CGROUPS_DIR_ENTRY_SIZE: usize = 8;
1141
-
1142
- #[test]
1143
- fn cgroups_resp_empty() {
1144
- let mut buf = [0u8; 4096];
1145
- let b = CgroupsBuilder::new(&mut buf, 0, 1, 42);
1146
- let total = b.finish();
1147
- assert_eq!(total, 24);
1148
-
1149
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1150
- assert_eq!(view.item_count, 0);
1151
- assert_eq!(view.systemd_enabled, 1);
1152
- assert_eq!(view.generation, 42);
1153
- }
1154
-
1155
- #[test]
1156
- fn cgroups_resp_single_item() {
1157
- let mut buf = [0u8; 4096];
1158
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 100);
1159
-
1160
- let name = b"docker-abc123";
1161
- let path = b"/sys/fs/cgroup/docker/abc123";
1162
- b.add(12345, 0x01, 1, name, path).unwrap();
1163
-
1164
- let total = b.finish();
1165
- assert!(total > 24);
1166
-
1167
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1168
- assert_eq!(view.item_count, 1);
1169
- assert_eq!(view.systemd_enabled, 0);
1170
- assert_eq!(view.generation, 100);
1171
-
1172
- let item = view.item(0).unwrap();
1173
- assert_eq!(item.hash, 12345);
1174
- assert_eq!(item.options, 0x01);
1175
- assert_eq!(item.enabled, 1);
1176
- assert_eq!(item.name.len as usize, name.len());
1177
- assert_eq!(item.name.as_bytes(), name);
1178
- assert_eq!(item.name.bytes[name.len()], 0); // NUL
1179
- assert_eq!(item.path.len as usize, path.len());
1180
- assert_eq!(item.path.as_bytes(), path);
1181
- assert_eq!(item.path.bytes[path.len()], 0); // NUL
1182
- }
1183
-
1184
- #[test]
1185
- fn cgroups_resp_multiple_items() {
1186
- let mut buf = [0u8; 8192];
1187
- let mut b = CgroupsBuilder::new(&mut buf, 5, 1, 999);
1188
-
1189
- // Item 0
1190
- let n0 = b"init.scope";
1191
- let p0 = b"/sys/fs/cgroup/init.scope";
1192
- b.add(100, 0, 1, n0, p0).unwrap();
1193
-
1194
- // Item 1
1195
- let n1 = b"system.slice/docker-abc.scope";
1196
- let p1 = b"/sys/fs/cgroup/system.slice/docker-abc.scope";
1197
- b.add(200, 0x02, 0, n1, p1).unwrap();
1198
-
1199
- // Item 2 - empty strings
1200
- b.add(300, 0, 1, b"", b"").unwrap();
1201
-
1202
- let total = b.finish();
1203
-
1204
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1205
- assert_eq!(view.item_count, 3);
1206
- assert_eq!(view.systemd_enabled, 1);
1207
- assert_eq!(view.generation, 999);
1208
-
1209
- // Verify item 0
1210
- let item = view.item(0).unwrap();
1211
- assert_eq!(item.hash, 100);
1212
- assert_eq!(item.name.len as usize, n0.len());
1213
- assert_eq!(item.name.as_bytes(), n0);
1214
- assert_eq!(item.path.len as usize, p0.len());
1215
- assert_eq!(item.path.as_bytes(), p0);
1216
-
1217
- // Verify item 1
1218
- let item = view.item(1).unwrap();
1219
- assert_eq!(item.hash, 200);
1220
- assert_eq!(item.options, 0x02);
1221
- assert_eq!(item.enabled, 0);
1222
- assert_eq!(item.name.len as usize, n1.len());
1223
- assert_eq!(item.name.as_bytes(), n1);
1224
-
1225
- // Verify item 2 (empty strings)
1226
- let item = view.item(2).unwrap();
1227
- assert_eq!(item.hash, 300);
1228
- assert_eq!(item.name.len, 0);
1229
- assert_eq!(item.name.bytes[0], 0); // NUL
1230
- assert_eq!(item.path.len, 0);
1231
- assert_eq!(item.path.bytes[0], 0); // NUL
1232
-
1233
- // Out-of-bounds index
1234
- assert_eq!(view.item(3), Err(NipcError::OutOfBounds));
1235
- }
1236
-
1237
- #[test]
1238
- fn cgroups_resp_decode_truncated_header() {
1239
- let buf = [0u8; 23];
1240
- assert_eq!(
1241
- CgroupsResponseView::decode(&buf).unwrap_err(),
1242
- NipcError::Truncated
1243
- );
1244
- }
1245
-
1246
- #[test]
1247
- fn cgroups_resp_decode_bad_layout() {
1248
- let mut buf = [0u8; 24];
1249
- buf[0..2].copy_from_slice(&99u16.to_ne_bytes());
1250
- assert_eq!(
1251
- CgroupsResponseView::decode(&buf).unwrap_err(),
1252
- NipcError::BadLayout
1253
- );
1254
- }
1255
-
1256
- #[test]
1257
- fn cgroups_resp_decode_truncated_dir() {
1258
- // Header says item_count=2 but payload is only 24 bytes
1259
- let mut buf = [0u8; 24];
1260
- buf[0..2].copy_from_slice(&1u16.to_ne_bytes());
1261
- buf[4..8].copy_from_slice(&2u32.to_ne_bytes());
1262
- assert_eq!(
1263
- CgroupsResponseView::decode(&buf).unwrap_err(),
1264
- NipcError::Truncated
1265
- );
1266
- }
1267
-
1268
- #[test]
1269
- fn cgroups_resp_decode_oob_dir() {
1270
- // Header + 1 dir entry pointing beyond payload
1271
- let mut buf = [0u8; 64];
1272
- buf[0..2].copy_from_slice(&1u16.to_ne_bytes());
1273
- buf[4..8].copy_from_slice(&1u32.to_ne_bytes());
1274
- // Dir entry at offset 24: offset=0, length=9999
1275
- buf[24..28].copy_from_slice(&0u32.to_ne_bytes());
1276
- buf[28..32].copy_from_slice(&9999u32.to_ne_bytes());
1277
- assert_eq!(
1278
- CgroupsResponseView::decode(&buf).unwrap_err(),
1279
- NipcError::OutOfBounds
1280
- );
1281
- }
1282
-
1283
- #[test]
1284
- fn cgroups_resp_decode_item_too_small() {
1285
- // Dir entry with length < 32
1286
- let mut buf = [0u8; 64];
1287
- buf[0..2].copy_from_slice(&1u16.to_ne_bytes());
1288
- buf[4..8].copy_from_slice(&1u32.to_ne_bytes());
1289
- buf[24..28].copy_from_slice(&0u32.to_ne_bytes());
1290
- buf[28..32].copy_from_slice(&16u32.to_ne_bytes());
1291
- assert_eq!(
1292
- CgroupsResponseView::decode(&buf).unwrap_err(),
1293
- NipcError::Truncated
1294
- );
1295
- }
1296
-
1297
- #[test]
1298
- fn cgroups_resp_item_missing_nul() {
1299
- // Build valid snapshot then corrupt the NUL terminator
1300
- let mut buf = [0u8; 4096];
1301
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1302
- b.add(1, 0, 1, b"test", b"/test").unwrap();
1303
- let total = b.finish();
1304
-
1305
- // Find item data and corrupt the name's NUL terminator
1306
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1307
- let item_off = u32::from_ne_bytes(
1308
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1309
- .try_into()
1310
- .unwrap(),
1311
- ) as usize;
1312
- let item_start = dir_end + item_off;
1313
-
1314
- let noff =
1315
- u32::from_ne_bytes(buf[item_start + 16..item_start + 20].try_into().unwrap()) as usize;
1316
- let nlen =
1317
- u32::from_ne_bytes(buf[item_start + 20..item_start + 24].try_into().unwrap()) as usize;
1318
-
1319
- buf[item_start + noff + nlen] = b'X'; // corrupt NUL
1320
-
1321
- // Re-decode after corruption -- header/dir still valid
1322
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1323
- assert_eq!(view.item(0).unwrap_err(), NipcError::MissingNul);
1324
- }
1325
-
1326
- #[test]
1327
- fn cgroups_resp_item_string_oob() {
1328
- // Build valid snapshot then corrupt string length to be huge
1329
- let mut buf = [0u8; 4096];
1330
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1331
- b.add(1, 0, 1, b"test", b"/test").unwrap();
1332
- let total = b.finish();
1333
-
1334
- // Corrupt name_length to huge value
1335
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1336
- let item_off = u32::from_ne_bytes(
1337
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1338
- .try_into()
1339
- .unwrap(),
1340
- ) as usize;
1341
- let item_start = dir_end + item_off;
1342
-
1343
- buf[item_start + 20..item_start + 24].copy_from_slice(&99999u32.to_ne_bytes());
1344
-
1345
- // Re-decode after corruption
1346
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1347
- assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
1348
- }
1349
-
1350
- #[test]
1351
- fn cgroups_builder_overflow() {
1352
- let mut buf = [0u8; 64]; // too small for any real item
1353
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 0);
1354
- let long_name = [b'A'; 200];
1355
- assert_eq!(b.add(1, 0, 1, &long_name, b""), Err(NipcError::Overflow));
1356
- }
1357
-
1358
- #[test]
1359
- fn cgroups_builder_max_items_exceeded() {
1360
- let mut buf = [0u8; 4096];
1361
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 0);
1362
- b.add(1, 0, 1, b"a", b"b").unwrap();
1363
- assert_eq!(b.add(2, 0, 1, b"c", b"d"), Err(NipcError::Overflow));
1364
- }
1365
-
1366
- #[test]
1367
- fn cgroups_builder_compaction() {
1368
- let mut buf = [0u8; 4096];
1369
- // Reserve 10 directory slots but only add 2 items
1370
- let mut b = CgroupsBuilder::new(&mut buf, 10, 1, 77);
1371
-
1372
- b.add(10, 0, 1, b"slice-a", b"/cgroup/slice-a").unwrap();
1373
- b.add(20, 0, 0, b"slice-b", b"/cgroup/slice-b").unwrap();
1374
-
1375
- let total = b.finish();
1376
-
1377
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1378
- assert_eq!(view.item_count, 2);
1379
- assert_eq!(view.generation, 77);
1380
-
1381
- let item = view.item(0).unwrap();
1382
- assert_eq!(item.hash, 10);
1383
- assert_eq!(item.name.as_bytes(), b"slice-a");
1384
-
1385
- let item = view.item(1).unwrap();
1386
- assert_eq!(item.hash, 20);
1387
- assert_eq!(item.name.as_bytes(), b"slice-b");
1388
- }
1389
-
1390
- // -----------------------------------------------------------------------
1391
- // Alignment utility test
1392
- // -----------------------------------------------------------------------
1393
-
1394
- #[test]
1395
- fn test_align8() {
1396
- assert_eq!(align8(0), 0);
1397
- assert_eq!(align8(1), 8);
1398
- assert_eq!(align8(7), 8);
1399
- assert_eq!(align8(8), 8);
1400
- assert_eq!(align8(9), 16);
1401
- assert_eq!(align8(16), 16);
1402
- assert_eq!(align8(17), 24);
1403
- }
1404
-
1405
- // -----------------------------------------------------------------------
1406
- // Cross-language wire compatibility: C-Rust byte identity
1407
- //
1408
- // These tests encode in Rust and verify the exact bytes match what the
1409
- // C implementation produces for the same inputs. This ensures identical
1410
- // wire output across languages.
1411
- // -----------------------------------------------------------------------
1412
-
1413
- #[test]
1414
- fn c_rust_header_bytes_identical() {
1415
- // Encode in Rust
1416
- let h = Header {
1417
- magic: MAGIC_MSG,
1418
- version: VERSION,
1419
- header_len: HEADER_LEN,
1420
- kind: KIND_REQUEST,
1421
- flags: FLAG_BATCH,
1422
- code: METHOD_CGROUPS_SNAPSHOT,
1423
- transport_status: STATUS_OK,
1424
- payload_len: 12345,
1425
- item_count: 42,
1426
- message_id: 0xDEAD_BEEF_CAFE_BABE,
1427
- };
1428
- let mut rust_buf = [0u8; 32];
1429
- h.encode(&mut rust_buf);
1430
-
1431
- // Known LE bytes for this header
1432
- let expected: [u8; 32] = [
1433
- 0x43, 0x50, 0x49, 0x4e, // magic
1434
- 0x01, 0x00, // version
1435
- 0x20, 0x00, // header_len
1436
- 0x01, 0x00, // kind
1437
- 0x01, 0x00, // flags
1438
- 0x02, 0x00, // code
1439
- 0x00, 0x00, // transport_status
1440
- 0x39, 0x30, 0x00, 0x00, // payload_len = 12345
1441
- 0x2a, 0x00, 0x00, 0x00, // item_count = 42
1442
- 0xbe, 0xba, 0xfe, 0xca, 0xef, 0xbe, 0xad, 0xde, // message_id
1443
- ];
1444
- assert_eq!(rust_buf, expected);
1445
- }
1446
-
1447
- #[test]
1448
- fn c_rust_chunk_bytes_identical() {
1449
- let c = ChunkHeader {
1450
- magic: MAGIC_CHUNK,
1451
- version: VERSION,
1452
- flags: 0,
1453
- message_id: 1,
1454
- total_message_len: 256,
1455
- chunk_index: 1,
1456
- chunk_count: 3,
1457
- chunk_payload_len: 100,
1458
- };
1459
- let mut rust_buf = [0u8; 32];
1460
- c.encode(&mut rust_buf);
1461
-
1462
- let expected: [u8; 32] = [
1463
- 0x4b, 0x48, 0x43, 0x4e, // magic
1464
- 0x01, 0x00, // version
1465
- 0x00, 0x00, // flags
1466
- 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // message_id
1467
- 0x00, 0x01, 0x00, 0x00, // total_message_len = 256
1468
- 0x01, 0x00, 0x00, 0x00, // chunk_index
1469
- 0x03, 0x00, 0x00, 0x00, // chunk_count
1470
- 0x64, 0x00, 0x00, 0x00, // chunk_payload_len = 100
1471
- ];
1472
- assert_eq!(rust_buf, expected);
1473
- }
1474
-
1475
- #[test]
1476
- fn c_rust_hello_bytes_identical() {
1477
- let h = Hello {
1478
- layout_version: 1,
1479
- flags: 0,
1480
- supported_profiles: PROFILE_BASELINE | PROFILE_SHM_FUTEX,
1481
- preferred_profiles: PROFILE_SHM_FUTEX,
1482
- max_request_payload_bytes: 4096,
1483
- max_request_batch_items: 100,
1484
- max_response_payload_bytes: 1048576,
1485
- max_response_batch_items: 1,
1486
- auth_token: 0xAABB_CCDD_EEFF_0011,
1487
- packet_size: 65536,
1488
- };
1489
-
1490
- let mut rust_buf = [0u8; 44];
1491
- h.encode(&mut rust_buf);
1492
-
1493
- // Verify key byte positions
1494
- assert_eq!(&rust_buf[0..2], &[0x01, 0x00]); // layout_version
1495
- assert_eq!(&rust_buf[2..4], &[0x00, 0x00]); // flags
1496
- assert_eq!(&rust_buf[4..8], &[0x05, 0x00, 0x00, 0x00]); // supported = 0x05
1497
- assert_eq!(&rust_buf[8..12], &[0x04, 0x00, 0x00, 0x00]); // preferred = 0x04
1498
- assert_eq!(&rust_buf[28..32], &[0x00, 0x00, 0x00, 0x00]); // padding = 0
1499
- assert_eq!(
1500
- &rust_buf[32..40],
1501
- &[0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA]
1502
- ); // auth_token
1503
-
1504
- // Round-trip
1505
- let out = Hello::decode(&rust_buf).unwrap();
1506
- assert_eq!(out, h);
1507
- }
1508
-
1509
- #[test]
1510
- fn c_rust_hello_ack_bytes_identical() {
1511
- let h = HelloAck {
1512
- layout_version: 1,
1513
- flags: 0,
1514
- server_supported_profiles: 0x07,
1515
- intersection_profiles: 0x05,
1516
- selected_profile: PROFILE_SHM_FUTEX,
1517
- agreed_max_request_payload_bytes: 2048,
1518
- agreed_max_request_batch_items: 50,
1519
- agreed_max_response_payload_bytes: 65536,
1520
- agreed_max_response_batch_items: 1,
1521
- agreed_packet_size: 32768,
1522
- session_id: 0x0000_0001_0000_0007,
1523
- };
1524
- let mut rust_buf = [0u8; 48];
1525
- h.encode(&mut rust_buf);
1526
-
1527
- assert_eq!(&rust_buf[0..2], &[0x01, 0x00]);
1528
- assert_eq!(&rust_buf[4..8], &[0x07, 0x00, 0x00, 0x00]); // server_supported
1529
- assert_eq!(&rust_buf[12..16], &[0x04, 0x00, 0x00, 0x00]); // selected = SHM_FUTEX
1530
- assert_eq!(&rust_buf[36..40], &[0x00, 0x00, 0x00, 0x00]); // padding
1531
- assert_eq!(
1532
- &rust_buf[40..48],
1533
- &[0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]
1534
- ); // session_id LE
1535
-
1536
- let out = HelloAck::decode(&rust_buf).unwrap();
1537
- assert_eq!(out, h);
1538
- }
1539
-
1540
- #[test]
1541
- fn c_rust_cgroups_req_bytes_identical() {
1542
- let r = CgroupsRequest {
1543
- layout_version: 1,
1544
- flags: 0,
1545
- };
1546
- let mut rust_buf = [0u8; 4];
1547
- r.encode(&mut rust_buf);
1548
-
1549
- assert_eq!(rust_buf, [0x01, 0x00, 0x00, 0x00]);
1550
-
1551
- let out = CgroupsRequest::decode(&rust_buf).unwrap();
1552
- assert_eq!(out, r);
1553
- }
1554
-
1555
- #[test]
1556
- fn c_rust_cgroups_snapshot_bytes_identical() {
1557
- // Build a snapshot with the exact same inputs as the C test
1558
- let mut buf = [0u8; 4096];
1559
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 100);
1560
- b.add(
1561
- 12345,
1562
- 0x01,
1563
- 1,
1564
- b"docker-abc123",
1565
- b"/sys/fs/cgroup/docker/abc123",
1566
- )
1567
- .unwrap();
1568
- let total = b.finish();
1569
-
1570
- // Verify the snapshot header bytes
1571
- assert_eq!(&buf[0..2], &[0x01, 0x00]); // layout_version
1572
- assert_eq!(&buf[2..4], &[0x00, 0x00]); // flags
1573
- assert_eq!(&buf[4..8], &[0x01, 0x00, 0x00, 0x00]); // item_count
1574
- assert_eq!(&buf[8..12], &[0x00, 0x00, 0x00, 0x00]); // systemd_enabled
1575
- assert_eq!(&buf[12..16], &[0x00, 0x00, 0x00, 0x00]); // reserved
1576
- assert_eq!(
1577
- &buf[16..24],
1578
- &[0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
1579
- ); // generation
1580
-
1581
- // Verify it decodes correctly
1582
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1583
- assert_eq!(view.item_count, 1);
1584
- assert_eq!(view.generation, 100);
1585
-
1586
- let item = view.item(0).unwrap();
1587
- assert_eq!(item.hash, 12345);
1588
- assert_eq!(item.name.as_bytes(), b"docker-abc123");
1589
- assert_eq!(item.path.as_bytes(), b"/sys/fs/cgroup/docker/abc123");
1590
- }
1591
-
1592
- #[test]
1593
- fn cgroups_resp_dir_bad_alignment() {
1594
- // Dir entry with unaligned offset
1595
- let mut buf = [0u8; 128];
1596
- buf[0..2].copy_from_slice(&1u16.to_ne_bytes());
1597
- buf[4..8].copy_from_slice(&1u32.to_ne_bytes());
1598
- // offset=3 (not 8-byte aligned), length=32
1599
- buf[24..28].copy_from_slice(&3u32.to_ne_bytes());
1600
- buf[28..32].copy_from_slice(&32u32.to_ne_bytes());
1601
- assert_eq!(
1602
- CgroupsResponseView::decode(&buf).unwrap_err(),
1603
- NipcError::BadAlignment
1604
- );
1605
- }
1606
-
1607
- #[test]
1608
- fn cgroups_resp_item_bad_layout_version() {
1609
- // Build valid snapshot, then corrupt the item's layout_version
1610
- let mut buf = [0u8; 4096];
1611
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1612
- b.add(1, 0, 1, b"test", b"/test").unwrap();
1613
- let total = b.finish();
1614
-
1615
- // Corrupt item layout_version
1616
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1617
- let item_off = u32::from_ne_bytes(
1618
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1619
- .try_into()
1620
- .unwrap(),
1621
- ) as usize;
1622
- let item_start = dir_end + item_off;
1623
- buf[item_start..item_start + 2].copy_from_slice(&99u16.to_ne_bytes());
1624
-
1625
- // Re-decode after corruption
1626
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1627
- assert_eq!(view.item(0).unwrap_err(), NipcError::BadLayout);
1628
- }
1629
-
1630
- #[test]
1631
- fn cgroups_resp_item_name_off_below_header() {
1632
- // Build valid snapshot, then set name_offset < 32
1633
- let mut buf = [0u8; 4096];
1634
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1635
- b.add(1, 0, 1, b"test", b"/test").unwrap();
1636
- let total = b.finish();
1637
-
1638
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1639
- let item_off = u32::from_ne_bytes(
1640
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1641
- .try_into()
1642
- .unwrap(),
1643
- ) as usize;
1644
- let item_start = dir_end + item_off;
1645
- // Set name_offset to 0 (below header)
1646
- buf[item_start + 16..item_start + 20].copy_from_slice(&0u32.to_ne_bytes());
1647
-
1648
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1649
- assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
1650
- }
1651
-
1652
- #[test]
1653
- fn cgroups_resp_item_path_off_below_header() {
1654
- let mut buf = [0u8; 4096];
1655
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1656
- b.add(1, 0, 1, b"test", b"/test").unwrap();
1657
- let total = b.finish();
1658
-
1659
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1660
- let item_off = u32::from_ne_bytes(
1661
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1662
- .try_into()
1663
- .unwrap(),
1664
- ) as usize;
1665
- let item_start = dir_end + item_off;
1666
- // Set path_offset to 16 (below header)
1667
- buf[item_start + 24..item_start + 28].copy_from_slice(&16u32.to_ne_bytes());
1668
-
1669
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1670
- assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
1671
- }
1672
-
1673
- #[test]
1674
- fn cgroups_resp_item_path_missing_nul() {
1675
- let mut buf = [0u8; 4096];
1676
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1677
- b.add(1, 0, 1, b"test", b"/test").unwrap();
1678
- let total = b.finish();
1679
-
1680
- // Corrupt path NUL
1681
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1682
- let item_off = u32::from_ne_bytes(
1683
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1684
- .try_into()
1685
- .unwrap(),
1686
- ) as usize;
1687
- let item_start = dir_end + item_off;
1688
- let poff =
1689
- u32::from_ne_bytes(buf[item_start + 24..item_start + 28].try_into().unwrap()) as usize;
1690
- let plen =
1691
- u32::from_ne_bytes(buf[item_start + 28..item_start + 32].try_into().unwrap()) as usize;
1692
- buf[item_start + poff + plen] = b'X';
1693
-
1694
- // Re-decode after corruption
1695
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1696
- assert_eq!(view.item(0).unwrap_err(), NipcError::MissingNul);
1697
- }
1698
-
1699
- #[test]
1700
- fn cgroups_resp_item_overlap_rejected() {
1701
- // Build a valid item, then manually set path_offset to overlap with name
1702
- let mut buf = [0u8; 4096];
1703
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1704
- b.add(1, 0, 1, b"hello", b"/path").unwrap();
1705
- let total = b.finish();
1706
-
1707
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1708
- let item_off = u32::from_ne_bytes(
1709
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1710
- .try_into()
1711
- .unwrap(),
1712
- ) as usize;
1713
- let item_start = dir_end + item_off;
1714
-
1715
- // name_off=32, name_len=5, so name region is [32..38)
1716
- // Set path_off=34 (inside name region), path_len=1
1717
- buf[item_start + 24..item_start + 28].copy_from_slice(&34u32.to_ne_bytes());
1718
- buf[item_start + 28..item_start + 32].copy_from_slice(&1u32.to_ne_bytes());
1719
- // Ensure NUL at item[34+1]=item[35]
1720
- buf[item_start + 35] = 0;
1721
-
1722
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1723
- assert_eq!(view.item(0).unwrap_err(), NipcError::BadLayout);
1724
- }
1725
-
1726
- // -------------------------------------------------------------------
1727
- // Proptest: fuzz / property-based tests for all decode paths
1728
- // -------------------------------------------------------------------
1729
-
1730
- mod proptests {
1731
- use super::*;
1732
- use proptest::prelude::*;
1733
-
1734
- // Arbitrary bytes -- no decode path may panic on any input.
1735
-
1736
- proptest! {
1737
- #[test]
1738
- fn decode_header_never_panics(data: Vec<u8>) {
1739
- let _ = Header::decode(&data);
1740
- }
1741
-
1742
- #[test]
1743
- fn decode_chunk_header_never_panics(data: Vec<u8>) {
1744
- let _ = ChunkHeader::decode(&data);
1745
- }
1746
-
1747
- #[test]
1748
- fn decode_hello_never_panics(data: Vec<u8>) {
1749
- let _ = Hello::decode(&data);
1750
- }
1751
-
1752
- #[test]
1753
- fn decode_hello_ack_never_panics(data: Vec<u8>) {
1754
- let _ = HelloAck::decode(&data);
1755
- }
1756
-
1757
- #[test]
1758
- fn decode_cgroups_request_never_panics(data: Vec<u8>) {
1759
- let _ = CgroupsRequest::decode(&data);
1760
- }
1761
-
1762
- #[test]
1763
- fn decode_cgroups_response_never_panics(data: Vec<u8>) {
1764
- let result = CgroupsResponseView::decode(&data);
1765
- if let Ok(view) = result {
1766
- // Exercise item access on valid decodes.
1767
- let limit = view.item_count.min(64);
1768
- for i in 0..limit {
1769
- let _ = view.item(i);
1770
- }
1771
- // Out-of-bounds must not panic.
1772
- let _ = view.item(view.item_count);
1773
- }
1774
- }
1775
-
1776
- #[test]
1777
- fn batch_dir_decode_never_panics(
1778
- data: Vec<u8>,
1779
- item_count in 0u32..128,
1780
- packed_area_len in 0u32..65536,
1781
- ) {
1782
- let _ = batch_dir_decode(&data, item_count, packed_area_len);
1783
- }
1784
-
1785
- #[test]
1786
- fn batch_item_get_never_panics(
1787
- data: Vec<u8>,
1788
- item_count in 0u32..128,
1789
- index in 0u32..128,
1790
- ) {
1791
- let _ = batch_item_get(&data, item_count, index);
1792
- }
1793
- }
1794
-
1795
- // Roundtrip tests: encode random valid values, decode, verify match.
1796
-
1797
- proptest! {
1798
- #[test]
1799
- fn encode_decode_header_roundtrip(
1800
- kind in 1u16..=3,
1801
- flags in any::<u16>(),
1802
- code in any::<u16>(),
1803
- transport_status in any::<u16>(),
1804
- payload_len in any::<u32>(),
1805
- item_count in any::<u32>(),
1806
- message_id in any::<u64>(),
1807
- ) {
1808
- let h = Header {
1809
- magic: MAGIC_MSG,
1810
- version: VERSION,
1811
- header_len: HEADER_LEN,
1812
- kind,
1813
- flags,
1814
- code,
1815
- transport_status,
1816
- payload_len,
1817
- item_count,
1818
- message_id,
1819
- };
1820
- let mut buf = [0u8; 64];
1821
- let n = h.encode(&mut buf);
1822
- prop_assert_eq!(n, HEADER_SIZE);
1823
- let decoded = Header::decode(&buf[..n]).unwrap();
1824
- prop_assert_eq!(decoded, h);
1825
- }
1826
-
1827
- #[test]
1828
- fn encode_decode_hello_roundtrip(
1829
- supported in any::<u32>(),
1830
- preferred in any::<u32>(),
1831
- max_req_payload in any::<u32>(),
1832
- max_req_batch in any::<u32>(),
1833
- max_resp_payload in any::<u32>(),
1834
- max_resp_batch in any::<u32>(),
1835
- auth_token in any::<u64>(),
1836
- packet_size in any::<u32>(),
1837
- ) {
1838
- let h = Hello {
1839
- layout_version: 1,
1840
- flags: 0,
1841
- supported_profiles: supported,
1842
- preferred_profiles: preferred,
1843
- max_request_payload_bytes: max_req_payload,
1844
- max_request_batch_items: max_req_batch,
1845
- max_response_payload_bytes: max_resp_payload,
1846
- max_response_batch_items: max_resp_batch,
1847
- auth_token,
1848
- packet_size,
1849
- };
1850
- let mut buf = [0u8; 64];
1851
- let n = h.encode(&mut buf);
1852
- prop_assert_eq!(n, HELLO_SIZE);
1853
- let decoded = Hello::decode(&buf[..n]).unwrap();
1854
- prop_assert_eq!(decoded, h);
1855
- }
1856
- }
1857
- }
1858
-
1859
- // -----------------------------------------------------------------------
1860
- // NipcError Display coverage
1861
- // -----------------------------------------------------------------------
1862
-
1863
- #[test]
1864
- fn nipc_error_display_all_variants() {
1865
- // Exercise the Display impl for every NipcError variant (lines 101-113)
1866
- let cases: Vec<(NipcError, &str)> = vec![
1867
- (NipcError::Truncated, "buffer too short"),
1868
- (NipcError::BadMagic, "magic value mismatch"),
1869
- (NipcError::BadVersion, "unsupported version"),
1870
- (NipcError::BadHeaderLen, "header_len != 32"),
1871
- (NipcError::BadKind, "unknown message kind"),
1872
- (NipcError::BadLayout, "unknown layout_version"),
1873
- (NipcError::OutOfBounds, "offset+length exceeds data"),
1874
- (NipcError::MissingNul, "string not NUL-terminated"),
1875
- (NipcError::BadAlignment, "item not 8-byte aligned"),
1876
- (NipcError::BadItemCount, "item count inconsistent"),
1877
- (NipcError::Overflow, "builder out of space"),
1878
- ];
1879
- for (err, expected) in cases {
1880
- let msg = format!("{}", err);
1881
- assert_eq!(msg, expected, "Display for {:?}", err);
1882
- }
1883
- // Also verify std::error::Error is implemented
1884
- let err: &dyn std::error::Error = &NipcError::Truncated;
1885
- let _ = format!("{err}");
1886
- }
1887
-
1888
- // -----------------------------------------------------------------------
1889
- // ChunkHeader decode: flags != 0 and chunk_payload_len == 0
1890
- // -----------------------------------------------------------------------
1891
-
1892
- #[test]
1893
- fn chunk_decode_bad_flags() {
1894
- // Line 257: flags != 0 -> BadLayout
1895
- let c = ChunkHeader {
1896
- magic: MAGIC_CHUNK,
1897
- version: VERSION,
1898
- flags: 0x01, // non-zero flags
1899
- message_id: 1,
1900
- total_message_len: 100,
1901
- chunk_index: 0,
1902
- chunk_count: 1,
1903
- chunk_payload_len: 50,
1904
- };
1905
- let mut buf = [0u8; 32];
1906
- c.encode(&mut buf);
1907
- assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::BadLayout));
1908
- }
1909
-
1910
- #[test]
1911
- fn chunk_decode_zero_payload_len() {
1912
- // Line 260: chunk_payload_len == 0 -> BadLayout
1913
- let c = ChunkHeader {
1914
- magic: MAGIC_CHUNK,
1915
- version: VERSION,
1916
- flags: 0,
1917
- message_id: 1,
1918
- total_message_len: 100,
1919
- chunk_index: 0,
1920
- chunk_count: 1,
1921
- chunk_payload_len: 0,
1922
- };
1923
- let mut buf = [0u8; 32];
1924
- c.encode(&mut buf);
1925
- assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::BadLayout));
1926
- }
1927
-
1928
- // -----------------------------------------------------------------------
1929
- // batch_dir_encode: buffer too small (line 282)
1930
- // -----------------------------------------------------------------------
1931
-
1932
- #[test]
1933
- fn batch_dir_encode_too_small() {
1934
- let entries = [
1935
- BatchEntry {
1936
- offset: 0,
1937
- length: 8,
1938
- },
1939
- BatchEntry {
1940
- offset: 8,
1941
- length: 8,
1942
- },
1943
- ];
1944
- let mut buf = [0u8; 12]; // needs 16, only 12
1945
- assert_eq!(batch_dir_encode(&entries, &mut buf), 0);
1946
- }
1947
-
1948
- // -----------------------------------------------------------------------
1949
- // batch_dir_validate error paths (lines 329, 336, 339)
1950
- // -----------------------------------------------------------------------
1951
-
1952
- #[test]
1953
- fn batch_dir_validate_truncated() {
1954
- let buf = [0u8; 4]; // too short for 1 entry (needs 8)
1955
- assert_eq!(batch_dir_validate(&buf, 1, 100), Err(NipcError::Truncated));
1956
- }
1957
-
1958
- #[test]
1959
- fn batch_dir_validate_bad_alignment() {
1960
- let mut buf = [0u8; 8];
1961
- buf[0..4].copy_from_slice(&3u32.to_ne_bytes()); // unaligned offset
1962
- buf[4..8].copy_from_slice(&8u32.to_ne_bytes());
1963
- assert_eq!(
1964
- batch_dir_validate(&buf, 1, 100),
1965
- Err(NipcError::BadAlignment)
1966
- );
1967
- }
1968
-
1969
- #[test]
1970
- fn batch_dir_validate_out_of_bounds() {
1971
- let mut buf = [0u8; 8];
1972
- buf[0..4].copy_from_slice(&0u32.to_ne_bytes());
1973
- buf[4..8].copy_from_slice(&200u32.to_ne_bytes()); // exceeds packed_area_len
1974
- assert_eq!(
1975
- batch_dir_validate(&buf, 1, 100),
1976
- Err(NipcError::OutOfBounds)
1977
- );
1978
- }
1979
-
1980
- #[test]
1981
- fn batch_dir_validate_ok() {
1982
- let mut buf = [0u8; 16];
1983
- buf[0..4].copy_from_slice(&0u32.to_ne_bytes());
1984
- buf[4..8].copy_from_slice(&8u32.to_ne_bytes());
1985
- buf[8..12].copy_from_slice(&8u32.to_ne_bytes());
1986
- buf[12..16].copy_from_slice(&8u32.to_ne_bytes());
1987
- assert!(batch_dir_validate(&buf, 2, 100).is_ok());
1988
- }
1989
-
1990
- // -----------------------------------------------------------------------
1991
- // batch_item_get: alignment check (line 375)
1992
- // -----------------------------------------------------------------------
1993
-
1994
- #[test]
1995
- fn batch_item_get_bad_alignment() {
1996
- // Manually craft a batch payload with unaligned offset
1997
- let mut buf = [0u8; 64];
1998
- // Directory: 1 entry at offset 0 of buf
1999
- buf[0..4].copy_from_slice(&3u32.to_ne_bytes()); // unaligned offset
2000
- buf[4..8].copy_from_slice(&4u32.to_ne_bytes());
2001
- assert_eq!(batch_item_get(&buf, 1, 0), Err(NipcError::BadAlignment));
2002
- }
2003
-
2004
- #[test]
2005
- fn batch_item_get_truncated_dir() {
2006
- // Payload too small to hold the directory
2007
- let buf = [0u8; 4]; // needs at least 8 for 1 item directory
2008
- assert_eq!(batch_item_get(&buf, 1, 0), Err(NipcError::Truncated));
2009
- }
2010
-
2011
- // -----------------------------------------------------------------------
2012
- // BatchBuilder::finish compaction (lines 451-456)
2013
- // -----------------------------------------------------------------------
2014
-
2015
- #[test]
2016
- fn batch_builder_compaction() {
2017
- // Reserve space for 8 items but add only 2 -- triggers compaction
2018
- let mut buf = [0u8; 1024];
2019
- let mut b = BatchBuilder::new(&mut buf, 8);
2020
-
2021
- let item1 = [1u8, 2, 3, 4, 5, 6, 7, 8];
2022
- let item2 = [10u8, 20, 30, 40];
2023
-
2024
- b.add(&item1).unwrap();
2025
- b.add(&item2).unwrap();
2026
-
2027
- // dir_end for 8 items = align8(8*8) = 64
2028
- // final_dir_aligned for 2 items = align8(2*8) = 16
2029
- // This triggers the copy_within compaction branch (line 453-456)
2030
- let (total, count) = b.finish();
2031
- assert_eq!(count, 2);
2032
- assert!(total > 0);
2033
-
2034
- // Verify the items can still be extracted correctly
2035
- let (data, len) = batch_item_get(&buf[..total], 2, 0).unwrap();
2036
- assert_eq!(len as usize, item1.len());
2037
- assert_eq!(data, &item1);
2038
-
2039
- let (data, len) = batch_item_get(&buf[..total], 2, 1).unwrap();
2040
- assert_eq!(len as usize, item2.len());
2041
- assert_eq!(data, &item2);
2042
- }
2043
-
2044
- // -----------------------------------------------------------------------
2045
- // HelloAck decode: flags != 0 (line 606)
2046
- // -----------------------------------------------------------------------
2047
-
2048
- #[test]
2049
- fn hello_ack_decode_bad_flags() {
2050
- let h = HelloAck {
2051
- layout_version: 1,
2052
- flags: 1, // non-zero flags
2053
- ..Default::default()
2054
- };
2055
- let mut buf = [0u8; 48];
2056
- h.encode(&mut buf);
2057
- assert_eq!(HelloAck::decode(&buf), Err(NipcError::BadLayout));
2058
- }
2059
-
2060
- // -----------------------------------------------------------------------
2061
- // Hello decode: non-zero padding (line 527)
2062
- // -----------------------------------------------------------------------
2063
-
2064
- #[test]
2065
- fn hello_decode_bad_padding() {
2066
- let h = Hello {
2067
- layout_version: 1,
2068
- flags: 0,
2069
- ..Default::default()
2070
- };
2071
- let mut buf = [0u8; 44];
2072
- h.encode(&mut buf);
2073
- // Corrupt the padding bytes at 28..32
2074
- buf[28..32].copy_from_slice(&1u32.to_ne_bytes());
2075
- assert_eq!(Hello::decode(&buf), Err(NipcError::BadLayout));
2076
- }
2077
-
2078
- // -----------------------------------------------------------------------
2079
- // Cgroups request: non-zero flags (line 45)
2080
- // -----------------------------------------------------------------------
2081
-
2082
- #[test]
2083
- fn cgroups_req_decode_bad_flags() {
2084
- let r = CgroupsRequest {
2085
- layout_version: 1,
2086
- flags: 1, // non-zero flags -> BadLayout
2087
- };
2088
- let mut buf = [0u8; 4];
2089
- r.encode(&mut buf);
2090
- assert_eq!(CgroupsRequest::decode(&buf), Err(NipcError::BadLayout));
2091
- }
2092
-
2093
- // -----------------------------------------------------------------------
2094
- // CgroupsResponseView: non-zero flags and reserved (lines 125, 130)
2095
- // -----------------------------------------------------------------------
2096
-
2097
- #[test]
2098
- fn cgroups_resp_decode_bad_flags() {
2099
- // Line 125: flags != 0 -> BadLayout
2100
- let mut buf = [0u8; 24];
2101
- buf[0..2].copy_from_slice(&1u16.to_ne_bytes()); // layout_version = 1
2102
- buf[2..4].copy_from_slice(&1u16.to_ne_bytes()); // flags = 1 (non-zero)
2103
- assert_eq!(
2104
- CgroupsResponseView::decode(&buf).unwrap_err(),
2105
- NipcError::BadLayout
2106
- );
2107
- }
2108
-
2109
- #[test]
2110
- fn cgroups_resp_decode_bad_reserved() {
2111
- // Line 130: reserved != 0 -> BadLayout
2112
- let mut buf = [0u8; 24];
2113
- buf[0..2].copy_from_slice(&1u16.to_ne_bytes()); // layout_version = 1
2114
- buf[2..4].copy_from_slice(&0u16.to_ne_bytes()); // flags = 0
2115
- buf[12..16].copy_from_slice(&1u32.to_ne_bytes()); // reserved = 1
2116
- assert_eq!(
2117
- CgroupsResponseView::decode(&buf).unwrap_err(),
2118
- NipcError::BadLayout
2119
- );
2120
- }
2121
-
2122
- // -----------------------------------------------------------------------
2123
- // CgroupsResponseView: bad alignment in directory (line 149)
2124
- // -----------------------------------------------------------------------
2125
-
2126
- #[test]
2127
- fn cgroups_resp_decode_bad_dir_alignment() {
2128
- // dir entry with offset not aligned to 8
2129
- let mut buf = [0u8; 128];
2130
- buf[0..2].copy_from_slice(&1u16.to_ne_bytes()); // layout_version
2131
- buf[4..8].copy_from_slice(&1u32.to_ne_bytes()); // item_count = 1
2132
- // Dir entry at offset 24: offset=3 (unaligned), length=32
2133
- buf[24..28].copy_from_slice(&3u32.to_ne_bytes());
2134
- buf[28..32].copy_from_slice(&32u32.to_ne_bytes());
2135
- assert_eq!(
2136
- CgroupsResponseView::decode(&buf).unwrap_err(),
2137
- NipcError::BadAlignment
2138
- );
2139
- }
2140
-
2141
- // -----------------------------------------------------------------------
2142
- // CgroupsItemView: bad layout_version, bad flags (lines 200-206)
2143
- // -----------------------------------------------------------------------
2144
-
2145
- #[test]
2146
- fn cgroups_item_bad_layout_version() {
2147
- // Build valid snapshot then corrupt item layout_version
2148
- let mut buf = [0u8; 4096];
2149
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
2150
- b.add(1, 0, 1, b"test", b"/test").unwrap();
2151
- let total = b.finish();
2152
-
2153
- // Find item start
2154
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
2155
- let item_off = u32::from_ne_bytes(
2156
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
2157
- .try_into()
2158
- .unwrap(),
2159
- ) as usize;
2160
- let item_start = dir_end + item_off;
2161
-
2162
- // Corrupt layout_version to 99
2163
- buf[item_start..item_start + 2].copy_from_slice(&99u16.to_ne_bytes());
2164
-
2165
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
2166
- assert_eq!(view.item(0).unwrap_err(), NipcError::BadLayout);
2167
- }
2168
-
2169
- #[test]
2170
- fn cgroups_item_bad_flags() {
2171
- // Build valid snapshot then corrupt item flags
2172
- let mut buf = [0u8; 4096];
2173
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
2174
- b.add(1, 0, 1, b"test", b"/test").unwrap();
2175
- let total = b.finish();
2176
-
2177
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
2178
- let item_off = u32::from_ne_bytes(
2179
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
2180
- .try_into()
2181
- .unwrap(),
2182
- ) as usize;
2183
- let item_start = dir_end + item_off;
2184
-
2185
- // Corrupt flags to non-zero
2186
- buf[item_start + 2..item_start + 4].copy_from_slice(&1u16.to_ne_bytes());
2187
-
2188
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
2189
- assert_eq!(view.item(0).unwrap_err(), NipcError::BadLayout);
2190
- }
2191
-
2192
- // -----------------------------------------------------------------------
2193
- // CgroupsItemView: name_off < ITEM_HDR_SIZE (line 211)
2194
- // -----------------------------------------------------------------------
2195
-
2196
- #[test]
2197
- fn cgroups_item_name_off_too_small() {
2198
- let mut buf = [0u8; 4096];
2199
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
2200
- b.add(1, 0, 1, b"test", b"/test").unwrap();
2201
- let total = b.finish();
2202
-
2203
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
2204
- let item_off = u32::from_ne_bytes(
2205
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
2206
- .try_into()
2207
- .unwrap(),
2208
- ) as usize;
2209
- let item_start = dir_end + item_off;
2210
-
2211
- // Set name_offset to 0 (< 32 = CGROUPS_ITEM_HDR_SIZE)
2212
- buf[item_start + 16..item_start + 20].copy_from_slice(&0u32.to_ne_bytes());
2213
-
2214
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
2215
- assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
2216
- }
2217
-
2218
- // -----------------------------------------------------------------------
2219
- // CgroupsItemView: path NUL missing (line 228)
2220
- // -----------------------------------------------------------------------
2221
-
2222
- #[test]
2223
- fn cgroups_item_path_missing_nul() {
2224
- let mut buf = [0u8; 4096];
2225
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
2226
- b.add(1, 0, 1, b"test", b"/test").unwrap();
2227
- let total = b.finish();
2228
-
2229
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
2230
- let item_off = u32::from_ne_bytes(
2231
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
2232
- .try_into()
2233
- .unwrap(),
2234
- ) as usize;
2235
- let item_start = dir_end + item_off;
2236
-
2237
- // Find the path NUL terminator and corrupt it
2238
- let path_off =
2239
- u32::from_ne_bytes(buf[item_start + 24..item_start + 28].try_into().unwrap()) as usize;
2240
- let path_len =
2241
- u32::from_ne_bytes(buf[item_start + 28..item_start + 32].try_into().unwrap()) as usize;
2242
- buf[item_start + path_off + path_len] = b'X'; // corrupt path NUL
2243
-
2244
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
2245
- assert_eq!(view.item(0).unwrap_err(), NipcError::MissingNul);
2246
- }
2247
-
2248
- // -----------------------------------------------------------------------
2249
- // CgroupsItemView: path_off < ITEM_HDR_SIZE (line 222)
2250
- // -----------------------------------------------------------------------
2251
-
2252
- #[test]
2253
- fn cgroups_item_path_off_too_small() {
2254
- let mut buf = [0u8; 4096];
2255
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
2256
- b.add(1, 0, 1, b"test", b"/test").unwrap();
2257
- let total = b.finish();
2258
-
2259
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
2260
- let item_off = u32::from_ne_bytes(
2261
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
2262
- .try_into()
2263
- .unwrap(),
2264
- ) as usize;
2265
- let item_start = dir_end + item_off;
2266
-
2267
- // Set path_offset to 0 (< 32 = CGROUPS_ITEM_HDR_SIZE)
2268
- buf[item_start + 24..item_start + 28].copy_from_slice(&0u32.to_ne_bytes());
2269
-
2270
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
2271
- assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
2272
- }
2273
-
2274
- // -----------------------------------------------------------------------
2275
- // CgroupsItemView: path string OOB (line 225)
2276
- // -----------------------------------------------------------------------
2277
-
2278
- #[test]
2279
- fn cgroups_item_path_string_oob() {
2280
- let mut buf = [0u8; 4096];
2281
- let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
2282
- b.add(1, 0, 1, b"test", b"/test").unwrap();
2283
- let total = b.finish();
2284
-
2285
- let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
2286
- let item_off = u32::from_ne_bytes(
2287
- buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
2288
- .try_into()
2289
- .unwrap(),
2290
- ) as usize;
2291
- let item_start = dir_end + item_off;
2292
-
2293
- // Corrupt path_length to huge value
2294
- buf[item_start + 28..item_start + 32].copy_from_slice(&99999u32.to_ne_bytes());
2295
-
2296
- let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
2297
- assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
2298
- }
2299
-
2300
- // -----------------------------------------------------------------------
2301
- // Cgroups dispatch (lines 438-447)
2302
- // -----------------------------------------------------------------------
2303
-
2304
- #[test]
2305
- fn dispatch_cgroups_snapshot_bad_request() {
2306
- // Bad request (too short) -> dispatch returns None (line 438)
2307
- let mut resp = [0u8; 4096];
2308
- let result =
2309
- crate::protocol::dispatch_cgroups_snapshot(&[], &mut resp, 1, |_req, _builder| true);
2310
- assert!(result.is_none());
2311
- }
2312
-
2313
- #[test]
2314
- fn dispatch_cgroups_snapshot_handler_returns_false() {
2315
- // Handler returns false -> dispatch returns None (lines 440-441)
2316
- let req = CgroupsRequest {
2317
- layout_version: 1,
2318
- flags: 0,
2319
- };
2320
- let mut req_buf = [0u8; 4];
2321
- req.encode(&mut req_buf);
2322
- let mut resp = [0u8; 4096];
2323
- let result =
2324
- crate::protocol::dispatch_cgroups_snapshot(&req_buf, &mut resp, 1, |_req, _builder| {
2325
- false
2326
- });
2327
- assert!(result.is_none());
2328
- }
2329
-
2330
- #[test]
2331
- fn dispatch_cgroups_snapshot_success() {
2332
- let req = CgroupsRequest {
2333
- layout_version: 1,
2334
- flags: 0,
2335
- };
2336
- let mut req_buf = [0u8; 4];
2337
- req.encode(&mut req_buf);
2338
- let mut resp = [0u8; 4096];
2339
- let result =
2340
- crate::protocol::dispatch_cgroups_snapshot(&req_buf, &mut resp, 2, |_req, builder| {
2341
- builder.add(1, 0, 1, b"cg1", b"/test").unwrap();
2342
- true
2343
- });
2344
- assert!(result.is_some());
2345
- let n = result.unwrap();
2346
- let view = CgroupsResponseView::decode(&resp[..n]).unwrap();
2347
- assert_eq!(view.item_count, 1);
2348
- }
2349
-
2350
- // -----------------------------------------------------------------------
2351
- // Dispatch increment / string_reverse buf overflow (lines 27, 58)
2352
- // -----------------------------------------------------------------------
2353
-
2354
- #[test]
2355
- fn dispatch_increment_resp_too_small() {
2356
- // Response buffer too small -> encode returns 0 -> dispatch returns None
2357
- let req_val = 42u64;
2358
- let mut req_buf = [0u8; 8];
2359
- crate::protocol::increment_encode(req_val, &mut req_buf);
2360
- let mut resp = [0u8; 4]; // too small for 8-byte response
2361
- let result = crate::protocol::dispatch_increment(&req_buf, &mut resp, |v| Some(v + 1));
2362
- assert!(result.is_none());
2363
- }
2364
-
2365
- #[test]
2366
- fn dispatch_increment_handler_none() {
2367
- let mut req_buf = [0u8; 8];
2368
- crate::protocol::increment_encode(42, &mut req_buf);
2369
- let mut resp = [0u8; 8];
2370
- let result = crate::protocol::dispatch_increment(&req_buf, &mut resp, |_| None);
2371
- assert!(result.is_none());
2372
- }
2373
-
2374
- #[test]
2375
- fn dispatch_string_reverse_resp_too_small() {
2376
- let s = b"hello";
2377
- let mut req_buf = [0u8; 64];
2378
- crate::protocol::string_reverse_encode(s, &mut req_buf);
2379
- let mut resp = [0u8; 4]; // too small
2380
- let result = crate::protocol::dispatch_string_reverse(&req_buf, &mut resp, |data| {
2381
- Some(data.iter().rev().copied().collect())
2382
- });
2383
- assert!(result.is_none());
2384
- }
2385
-
2386
- #[test]
2387
- fn dispatch_string_reverse_handler_none() {
2388
- let s = b"hello";
2389
- let mut req_buf = [0u8; 64];
2390
- crate::protocol::string_reverse_encode(s, &mut req_buf);
2391
- let mut resp = [0u8; 64];
2392
- let result = crate::protocol::dispatch_string_reverse(&req_buf, &mut resp, |_| None);
2393
- assert!(result.is_none());
2394
- }
2395
-}
624
+mod tests;
src/crates/netipc/src/protocol/tests.rs
new
+1828
@@ -0,0 +1,1828 @@
1
+use super::*;
2
+
3
+// -----------------------------------------------------------------------
4
+// Outer message header tests
5
+// -----------------------------------------------------------------------
6
+
7
+#[test]
8
+fn header_roundtrip() {
9
+ let h = Header {
10
+ magic: MAGIC_MSG,
11
+ version: VERSION,
12
+ header_len: HEADER_LEN,
13
+ kind: KIND_REQUEST,
14
+ flags: FLAG_BATCH,
15
+ code: METHOD_CGROUPS_SNAPSHOT,
16
+ transport_status: STATUS_OK,
17
+ payload_len: 12345,
18
+ item_count: 42,
19
+ message_id: 0xDEAD_BEEF_CAFE_BABE,
20
+ };
21
+
22
+ let mut buf = [0u8; 64];
23
+ let n = h.encode(&mut buf);
24
+ assert_eq!(n, 32);
25
+
26
+ let out = Header::decode(&buf[..n]).unwrap();
27
+ assert_eq!(out, h);
28
+}
29
+
30
+#[test]
31
+fn header_encode_too_small() {
32
+ let h = Header::default();
33
+ let mut buf = [0u8; 16];
34
+ assert_eq!(h.encode(&mut buf), 0);
35
+}
36
+
37
+#[test]
38
+fn header_decode_truncated() {
39
+ let buf = [0u8; 31];
40
+ assert_eq!(Header::decode(&buf), Err(NipcError::Truncated));
41
+}
42
+
43
+#[test]
44
+fn header_decode_bad_magic() {
45
+ let h = Header {
46
+ magic: 0x12345678,
47
+ version: VERSION,
48
+ header_len: HEADER_LEN,
49
+ kind: KIND_REQUEST,
50
+ ..Default::default()
51
+ };
52
+ let mut buf = [0u8; 32];
53
+ h.encode(&mut buf);
54
+ assert_eq!(Header::decode(&buf), Err(NipcError::BadMagic));
55
+}
56
+
57
+#[test]
58
+fn header_decode_bad_version() {
59
+ let h = Header {
60
+ magic: MAGIC_MSG,
61
+ version: 99,
62
+ header_len: HEADER_LEN,
63
+ kind: KIND_REQUEST,
64
+ ..Default::default()
65
+ };
66
+ let mut buf = [0u8; 32];
67
+ h.encode(&mut buf);
68
+ assert_eq!(Header::decode(&buf), Err(NipcError::BadVersion));
69
+}
70
+
71
+#[test]
72
+fn header_decode_bad_header_len() {
73
+ let h = Header {
74
+ magic: MAGIC_MSG,
75
+ version: VERSION,
76
+ header_len: 64,
77
+ kind: KIND_REQUEST,
78
+ ..Default::default()
79
+ };
80
+ let mut buf = [0u8; 32];
81
+ h.encode(&mut buf);
82
+ assert_eq!(Header::decode(&buf), Err(NipcError::BadHeaderLen));
83
+}
84
+
85
+#[test]
86
+fn header_decode_bad_kind() {
87
+ // kind = 0
88
+ let h = Header {
89
+ magic: MAGIC_MSG,
90
+ version: VERSION,
91
+ header_len: HEADER_LEN,
92
+ kind: 0,
93
+ ..Default::default()
94
+ };
95
+ let mut buf = [0u8; 32];
96
+ h.encode(&mut buf);
97
+ assert_eq!(Header::decode(&buf), Err(NipcError::BadKind));
98
+
99
+ // kind = 4
100
+ let h2 = Header { kind: 4, ..h };
101
+ h2.encode(&mut buf);
102
+ assert_eq!(Header::decode(&buf), Err(NipcError::BadKind));
103
+}
104
+
105
+#[test]
106
+fn header_all_kinds() {
107
+ for k in KIND_REQUEST..=KIND_CONTROL {
108
+ let h = Header {
109
+ magic: MAGIC_MSG,
110
+ version: VERSION,
111
+ header_len: HEADER_LEN,
112
+ kind: k,
113
+ ..Default::default()
114
+ };
115
+ let mut buf = [0u8; 32];
116
+ h.encode(&mut buf);
117
+ let out = Header::decode(&buf).unwrap();
118
+ assert_eq!(out.kind, k);
119
+ }
120
+}
121
+
122
+#[test]
123
+fn header_wire_bytes() {
124
+ let h = Header {
125
+ magic: MAGIC_MSG,
126
+ version: VERSION,
127
+ header_len: HEADER_LEN,
128
+ kind: KIND_REQUEST,
129
+ flags: 0,
130
+ code: METHOD_CGROUPS_SNAPSHOT,
131
+ transport_status: STATUS_OK,
132
+ payload_len: 4,
133
+ item_count: 1,
134
+ message_id: 1,
135
+ };
136
+
137
+ let mut buf = [0u8; 32];
138
+ h.encode(&mut buf);
139
+
140
+ // magic = 0x4e495043 LE: 43 50 49 4e
141
+ assert_eq!(&buf[0..4], &[0x43, 0x50, 0x49, 0x4e]);
142
+ // version = 1 LE: 01 00
143
+ assert_eq!(&buf[4..6], &[0x01, 0x00]);
144
+ // header_len = 32 LE: 20 00
145
+ assert_eq!(&buf[6..8], &[0x20, 0x00]);
146
+ // kind = 1 LE: 01 00
147
+ assert_eq!(&buf[8..10], &[0x01, 0x00]);
148
+ // code = 2 LE: 02 00
149
+ assert_eq!(&buf[12..14], &[0x02, 0x00]);
150
+}
151
+
152
+// -----------------------------------------------------------------------
153
+// Chunk continuation header tests
154
+// -----------------------------------------------------------------------
155
+
156
+#[test]
157
+fn chunk_header_roundtrip() {
158
+ let c = ChunkHeader {
159
+ magic: MAGIC_CHUNK,
160
+ version: VERSION,
161
+ flags: 0,
162
+ message_id: 0x1234_5678_90AB_CDEF,
163
+ total_message_len: 100000,
164
+ chunk_index: 3,
165
+ chunk_count: 10,
166
+ chunk_payload_len: 8192,
167
+ };
168
+
169
+ let mut buf = [0u8; 64];
170
+ let n = c.encode(&mut buf);
171
+ assert_eq!(n, 32);
172
+
173
+ let out = ChunkHeader::decode(&buf[..n]).unwrap();
174
+ assert_eq!(out, c);
175
+}
176
+
177
+#[test]
178
+fn chunk_decode_truncated() {
179
+ let buf = [0u8; 31];
180
+ assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::Truncated));
181
+}
182
+
183
+#[test]
184
+fn chunk_decode_bad_magic() {
185
+ let c = ChunkHeader {
186
+ magic: MAGIC_MSG, // wrong magic for chunk
187
+ version: VERSION,
188
+ ..Default::default()
189
+ };
190
+ let mut buf = [0u8; 32];
191
+ c.encode(&mut buf);
192
+ assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::BadMagic));
193
+}
194
+
195
+#[test]
196
+fn chunk_decode_bad_version() {
197
+ let c = ChunkHeader {
198
+ magic: MAGIC_CHUNK,
199
+ version: 2,
200
+ ..Default::default()
201
+ };
202
+ let mut buf = [0u8; 32];
203
+ c.encode(&mut buf);
204
+ assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::BadVersion));
205
+}
206
+
207
+#[test]
208
+fn chunk_encode_too_small() {
209
+ let c = ChunkHeader::default();
210
+ let mut buf = [0u8; 16];
211
+ assert_eq!(c.encode(&mut buf), 0);
212
+}
213
+
214
+#[test]
215
+fn chunk_wire_bytes() {
216
+ let c = ChunkHeader {
217
+ magic: MAGIC_CHUNK,
218
+ version: VERSION,
219
+ flags: 0,
220
+ message_id: 1,
221
+ total_message_len: 256,
222
+ chunk_index: 1,
223
+ chunk_count: 3,
224
+ chunk_payload_len: 100,
225
+ };
226
+
227
+ let mut buf = [0u8; 32];
228
+ c.encode(&mut buf);
229
+
230
+ // magic = 0x4e43484b LE: 4b 48 43 4e
231
+ assert_eq!(&buf[0..4], &[0x4b, 0x48, 0x43, 0x4e]);
232
+}
233
+
234
+// -----------------------------------------------------------------------
235
+// Batch item directory tests
236
+// -----------------------------------------------------------------------
237
+
238
+#[test]
239
+fn batch_dir_roundtrip() {
240
+ let entries = [
241
+ BatchEntry {
242
+ offset: 0,
243
+ length: 100,
244
+ },
245
+ BatchEntry {
246
+ offset: 104,
247
+ length: 200,
248
+ },
249
+ BatchEntry {
250
+ offset: 304,
251
+ length: 50,
252
+ },
253
+ ];
254
+
255
+ let mut buf = [0u8; 64];
256
+ let n = batch_dir_encode(&entries, &mut buf);
257
+ assert_eq!(n, 24);
258
+
259
+ let out = batch_dir_decode(&buf[..n], 3, 400).unwrap();
260
+ assert_eq!(out[0], entries[0]);
261
+ assert_eq!(out[1], entries[1]);
262
+ assert_eq!(out[2], entries[2]);
263
+}
264
+
265
+#[test]
266
+fn batch_dir_decode_truncated() {
267
+ let buf = [0u8; 12];
268
+ assert_eq!(batch_dir_decode(&buf, 2, 1000), Err(NipcError::Truncated));
269
+}
270
+
271
+#[test]
272
+fn batch_dir_decode_oob() {
273
+ let e = BatchEntry {
274
+ offset: 0,
275
+ length: 200,
276
+ };
277
+ let mut buf = [0u8; 8];
278
+ batch_dir_encode(&[e], &mut buf);
279
+ assert_eq!(batch_dir_decode(&buf, 1, 100), Err(NipcError::OutOfBounds));
280
+}
281
+
282
+#[test]
283
+fn batch_dir_decode_bad_alignment() {
284
+ let mut buf = [0u8; 8];
285
+ // Manually write unaligned offset
286
+ buf[0..4].copy_from_slice(&3u32.to_ne_bytes());
287
+ buf[4..8].copy_from_slice(&10u32.to_ne_bytes());
288
+ assert_eq!(batch_dir_decode(&buf, 1, 100), Err(NipcError::BadAlignment));
289
+}
290
+
291
+// -----------------------------------------------------------------------
292
+// Batch builder + extraction tests
293
+// -----------------------------------------------------------------------
294
+
295
+#[test]
296
+fn batch_builder_roundtrip() {
297
+ let mut buf = [0u8; 1024];
298
+ let mut b = BatchBuilder::new(&mut buf, 4);
299
+
300
+ let item1 = [1u8, 2, 3, 4, 5];
301
+ let item2 = [10u8, 20, 30];
302
+ let item3 = [0xAAu8, 0xBB];
303
+
304
+ b.add(&item1).unwrap();
305
+ b.add(&item2).unwrap();
306
+ b.add(&item3).unwrap();
307
+
308
+ let (total, count) = b.finish();
309
+ assert_eq!(count, 3);
310
+ assert!(total > 0);
311
+
312
+ // Extract items
313
+ let (data, len) = batch_item_get(&buf[..total], 3, 0).unwrap();
314
+ assert_eq!(len as usize, item1.len());
315
+ assert_eq!(data, &item1);
316
+
317
+ let (data, len) = batch_item_get(&buf[..total], 3, 1).unwrap();
318
+ assert_eq!(len as usize, item2.len());
319
+ assert_eq!(data, &item2);
320
+
321
+ let (data, len) = batch_item_get(&buf[..total], 3, 2).unwrap();
322
+ assert_eq!(len as usize, item3.len());
323
+ assert_eq!(data, &item3);
324
+}
325
+
326
+#[test]
327
+fn batch_builder_overflow() {
328
+ let mut buf = [0u8; 32];
329
+ let mut b = BatchBuilder::new(&mut buf, 1);
330
+ let item = [1u8];
331
+ b.add(&item).unwrap();
332
+ assert_eq!(b.add(&item), Err(NipcError::Overflow));
333
+}
334
+
335
+#[test]
336
+fn batch_builder_buf_overflow() {
337
+ let mut buf = [0u8; 24];
338
+ let mut b = BatchBuilder::new(&mut buf, 1);
339
+ let big = [0u8; 100];
340
+ assert_eq!(b.add(&big), Err(NipcError::Overflow));
341
+}
342
+
343
+#[test]
344
+fn batch_item_get_oob_index() {
345
+ let mut buf = [0u8; 64];
346
+ let mut b = BatchBuilder::new(&mut buf, 2);
347
+ b.add(&[1u8]).unwrap();
348
+ let (total, count) = b.finish();
349
+ assert_eq!(
350
+ batch_item_get(&buf[..total], count, 5),
351
+ Err(NipcError::OutOfBounds)
352
+ );
353
+}
354
+
355
+#[test]
356
+fn batch_empty() {
357
+ let mut buf = [0u8; 64];
358
+ let b = BatchBuilder::new(&mut buf, 4);
359
+ let (total, count) = b.finish();
360
+ assert_eq!(count, 0);
361
+ assert_eq!(total, 0);
362
+}
363
+
364
+// -----------------------------------------------------------------------
365
+// Hello payload tests
366
+// -----------------------------------------------------------------------
367
+
368
+#[test]
369
+fn hello_roundtrip() {
370
+ let h = Hello {
371
+ layout_version: 1,
372
+ flags: 0,
373
+ supported_profiles: PROFILE_BASELINE | PROFILE_SHM_FUTEX,
374
+ preferred_profiles: PROFILE_SHM_FUTEX,
375
+ max_request_payload_bytes: 4096,
376
+ max_request_batch_items: 100,
377
+ max_response_payload_bytes: 1048576,
378
+ max_response_batch_items: 1,
379
+ auth_token: 0xAABB_CCDD_EEFF_0011,
380
+ packet_size: 65536,
381
+ };
382
+
383
+ let mut buf = [0u8; 64];
384
+ let n = h.encode(&mut buf);
385
+ assert_eq!(n, 44);
386
+
387
+ let out = Hello::decode(&buf[..n]).unwrap();
388
+ assert_eq!(out, h);
389
+}
390
+
391
+#[test]
392
+fn hello_decode_truncated() {
393
+ let buf = [0u8; 43];
394
+ assert_eq!(Hello::decode(&buf), Err(NipcError::Truncated));
395
+}
396
+
397
+#[test]
398
+fn hello_decode_bad_layout() {
399
+ let h = Hello {
400
+ layout_version: 99,
401
+ ..Default::default()
402
+ };
403
+ let mut buf = [0u8; 44];
404
+ h.encode(&mut buf);
405
+ assert_eq!(Hello::decode(&buf), Err(NipcError::BadLayout));
406
+}
407
+
408
+#[test]
409
+fn hello_encode_too_small() {
410
+ let h = Hello::default();
411
+ let mut buf = [0u8; 10];
412
+ assert_eq!(h.encode(&mut buf), 0);
413
+}
414
+
415
+// -----------------------------------------------------------------------
416
+// Hello-ack payload tests
417
+// -----------------------------------------------------------------------
418
+
419
+#[test]
420
+fn hello_ack_roundtrip() {
421
+ let h = HelloAck {
422
+ layout_version: 1,
423
+ flags: 0,
424
+ server_supported_profiles: 0x07,
425
+ intersection_profiles: 0x05,
426
+ selected_profile: PROFILE_SHM_FUTEX,
427
+ agreed_max_request_payload_bytes: 2048,
428
+ agreed_max_request_batch_items: 50,
429
+ agreed_max_response_payload_bytes: 65536,
430
+ agreed_max_response_batch_items: 1,
431
+ agreed_packet_size: 32768,
432
+ session_id: 42,
433
+ };
434
+
435
+ let mut buf = [0u8; 64];
436
+ let n = h.encode(&mut buf);
437
+ assert_eq!(n, 48);
438
+
439
+ let out = HelloAck::decode(&buf[..n]).unwrap();
440
+ assert_eq!(out, h);
441
+}
442
+
443
+#[test]
444
+fn hello_ack_decode_truncated() {
445
+ let buf = [0u8; 47];
446
+ assert_eq!(HelloAck::decode(&buf), Err(NipcError::Truncated));
447
+}
448
+
449
+#[test]
450
+fn hello_ack_decode_bad_layout() {
451
+ let h = HelloAck {
452
+ layout_version: 0,
453
+ ..Default::default()
454
+ };
455
+ let mut buf = [0u8; 48];
456
+ h.encode(&mut buf);
457
+ assert_eq!(HelloAck::decode(&buf), Err(NipcError::BadLayout));
458
+}
459
+
460
+#[test]
461
+fn hello_ack_encode_too_small() {
462
+ let h = HelloAck::default();
463
+ let mut buf = [0u8; 10];
464
+ assert_eq!(h.encode(&mut buf), 0);
465
+}
466
+
467
+// -----------------------------------------------------------------------
468
+// Cgroups snapshot request tests
469
+// -----------------------------------------------------------------------
470
+
471
+#[test]
472
+fn cgroups_req_roundtrip() {
473
+ let r = CgroupsRequest {
474
+ layout_version: 1,
475
+ flags: 0,
476
+ };
477
+
478
+ let mut buf = [0u8; 16];
479
+ let n = r.encode(&mut buf);
480
+ assert_eq!(n, 4);
481
+
482
+ let out = CgroupsRequest::decode(&buf[..n]).unwrap();
483
+ assert_eq!(out, r);
484
+}
485
+
486
+#[test]
487
+fn cgroups_req_decode_truncated() {
488
+ let buf = [0u8; 3];
489
+ assert_eq!(CgroupsRequest::decode(&buf), Err(NipcError::Truncated));
490
+}
491
+
492
+#[test]
493
+fn cgroups_req_decode_bad_layout() {
494
+ let r = CgroupsRequest {
495
+ layout_version: 5,
496
+ flags: 0,
497
+ };
498
+ let mut buf = [0u8; 4];
499
+ r.encode(&mut buf);
500
+ assert_eq!(CgroupsRequest::decode(&buf), Err(NipcError::BadLayout));
501
+}
502
+
503
+#[test]
504
+fn cgroups_req_encode_too_small() {
505
+ let r = CgroupsRequest::default();
506
+ let mut buf = [0u8; 2];
507
+ assert_eq!(r.encode(&mut buf), 0);
508
+}
509
+
510
+// -----------------------------------------------------------------------
511
+// Cgroups snapshot response tests
512
+// -----------------------------------------------------------------------
513
+
514
+// Private constants needed by tests -- mirror cgroups_snapshot.rs values.
515
+const CGROUPS_RESP_HDR_SIZE: usize = 24;
516
+const CGROUPS_DIR_ENTRY_SIZE: usize = 8;
517
+
518
+#[test]
519
+fn cgroups_resp_empty() {
520
+ let mut buf = [0u8; 4096];
521
+ let b = CgroupsBuilder::new(&mut buf, 0, 1, 42);
522
+ let total = b.finish();
523
+ assert_eq!(total, 24);
524
+
525
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
526
+ assert_eq!(view.item_count, 0);
527
+ assert_eq!(view.systemd_enabled, 1);
528
+ assert_eq!(view.generation, 42);
529
+}
530
+
531
+#[test]
532
+fn cgroups_resp_single_item() {
533
+ let mut buf = [0u8; 4096];
534
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 100);
535
+
536
+ let name = b"docker-abc123";
537
+ let path = b"/sys/fs/cgroup/docker/abc123";
538
+ b.add(12345, 0x01, 1, name, path).unwrap();
539
+
540
+ let total = b.finish();
541
+ assert!(total > 24);
542
+
543
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
544
+ assert_eq!(view.item_count, 1);
545
+ assert_eq!(view.systemd_enabled, 0);
546
+ assert_eq!(view.generation, 100);
547
+
548
+ let item = view.item(0).unwrap();
549
+ assert_eq!(item.hash, 12345);
550
+ assert_eq!(item.options, 0x01);
551
+ assert_eq!(item.enabled, 1);
552
+ assert_eq!(item.name.len as usize, name.len());
553
+ assert_eq!(item.name.as_bytes(), name);
554
+ assert_eq!(item.name.bytes[name.len()], 0); // NUL
555
+ assert_eq!(item.path.len as usize, path.len());
556
+ assert_eq!(item.path.as_bytes(), path);
557
+ assert_eq!(item.path.bytes[path.len()], 0); // NUL
558
+}
559
+
560
+#[test]
561
+fn cgroups_resp_multiple_items() {
562
+ let mut buf = [0u8; 8192];
563
+ let mut b = CgroupsBuilder::new(&mut buf, 5, 1, 999);
564
+
565
+ // Item 0
566
+ let n0 = b"init.scope";
567
+ let p0 = b"/sys/fs/cgroup/init.scope";
568
+ b.add(100, 0, 1, n0, p0).unwrap();
569
+
570
+ // Item 1
571
+ let n1 = b"system.slice/docker-abc.scope";
572
+ let p1 = b"/sys/fs/cgroup/system.slice/docker-abc.scope";
573
+ b.add(200, 0x02, 0, n1, p1).unwrap();
574
+
575
+ // Item 2 - empty strings
576
+ b.add(300, 0, 1, b"", b"").unwrap();
577
+
578
+ let total = b.finish();
579
+
580
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
581
+ assert_eq!(view.item_count, 3);
582
+ assert_eq!(view.systemd_enabled, 1);
583
+ assert_eq!(view.generation, 999);
584
+
585
+ // Verify item 0
586
+ let item = view.item(0).unwrap();
587
+ assert_eq!(item.hash, 100);
588
+ assert_eq!(item.name.len as usize, n0.len());
589
+ assert_eq!(item.name.as_bytes(), n0);
590
+ assert_eq!(item.path.len as usize, p0.len());
591
+ assert_eq!(item.path.as_bytes(), p0);
592
+
593
+ // Verify item 1
594
+ let item = view.item(1).unwrap();
595
+ assert_eq!(item.hash, 200);
596
+ assert_eq!(item.options, 0x02);
597
+ assert_eq!(item.enabled, 0);
598
+ assert_eq!(item.name.len as usize, n1.len());
599
+ assert_eq!(item.name.as_bytes(), n1);
600
+
601
+ // Verify item 2 (empty strings)
602
+ let item = view.item(2).unwrap();
603
+ assert_eq!(item.hash, 300);
604
+ assert_eq!(item.name.len, 0);
605
+ assert_eq!(item.name.bytes[0], 0); // NUL
606
+ assert_eq!(item.path.len, 0);
607
+ assert_eq!(item.path.bytes[0], 0); // NUL
608
+
609
+ // Out-of-bounds index
610
+ assert_eq!(view.item(3), Err(NipcError::OutOfBounds));
611
+}
612
+
613
+#[test]
614
+fn cgroups_resp_decode_truncated_header() {
615
+ let buf = [0u8; 23];
616
+ assert_eq!(
617
+ CgroupsResponseView::decode(&buf).unwrap_err(),
618
+ NipcError::Truncated
619
+ );
620
+}
621
+
622
+#[test]
623
+fn cgroups_resp_decode_bad_layout() {
624
+ let mut buf = [0u8; 24];
625
+ buf[0..2].copy_from_slice(&99u16.to_ne_bytes());
626
+ assert_eq!(
627
+ CgroupsResponseView::decode(&buf).unwrap_err(),
628
+ NipcError::BadLayout
629
+ );
630
+}
631
+
632
+#[test]
633
+fn cgroups_resp_decode_truncated_dir() {
634
+ // Header says item_count=2 but payload is only 24 bytes
635
+ let mut buf = [0u8; 24];
636
+ buf[0..2].copy_from_slice(&1u16.to_ne_bytes());
637
+ buf[4..8].copy_from_slice(&2u32.to_ne_bytes());
638
+ assert_eq!(
639
+ CgroupsResponseView::decode(&buf).unwrap_err(),
640
+ NipcError::Truncated
641
+ );
642
+}
643
+
644
+#[test]
645
+fn cgroups_resp_decode_oob_dir() {
646
+ // Header + 1 dir entry pointing beyond payload
647
+ let mut buf = [0u8; 64];
648
+ buf[0..2].copy_from_slice(&1u16.to_ne_bytes());
649
+ buf[4..8].copy_from_slice(&1u32.to_ne_bytes());
650
+ // Dir entry at offset 24: offset=0, length=9999
651
+ buf[24..28].copy_from_slice(&0u32.to_ne_bytes());
652
+ buf[28..32].copy_from_slice(&9999u32.to_ne_bytes());
653
+ assert_eq!(
654
+ CgroupsResponseView::decode(&buf).unwrap_err(),
655
+ NipcError::OutOfBounds
656
+ );
657
+}
658
+
659
+#[test]
660
+fn cgroups_resp_decode_item_too_small() {
661
+ // Dir entry with length < 32
662
+ let mut buf = [0u8; 64];
663
+ buf[0..2].copy_from_slice(&1u16.to_ne_bytes());
664
+ buf[4..8].copy_from_slice(&1u32.to_ne_bytes());
665
+ buf[24..28].copy_from_slice(&0u32.to_ne_bytes());
666
+ buf[28..32].copy_from_slice(&16u32.to_ne_bytes());
667
+ assert_eq!(
668
+ CgroupsResponseView::decode(&buf).unwrap_err(),
669
+ NipcError::Truncated
670
+ );
671
+}
672
+
673
+#[test]
674
+fn cgroups_resp_item_missing_nul() {
675
+ // Build valid snapshot then corrupt the NUL terminator
676
+ let mut buf = [0u8; 4096];
677
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
678
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
679
+ let total = b.finish();
680
+
681
+ // Find item data and corrupt the name's NUL terminator
682
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
683
+ let item_off = u32::from_ne_bytes(
684
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
685
+ .try_into()
686
+ .unwrap(),
687
+ ) as usize;
688
+ let item_start = dir_end + item_off;
689
+
690
+ let noff =
691
+ u32::from_ne_bytes(buf[item_start + 16..item_start + 20].try_into().unwrap()) as usize;
692
+ let nlen =
693
+ u32::from_ne_bytes(buf[item_start + 20..item_start + 24].try_into().unwrap()) as usize;
694
+
695
+ buf[item_start + noff + nlen] = b'X'; // corrupt NUL
696
+
697
+ // Re-decode after corruption -- header/dir still valid
698
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
699
+ assert_eq!(view.item(0).unwrap_err(), NipcError::MissingNul);
700
+}
701
+
702
+#[test]
703
+fn cgroups_resp_item_string_oob() {
704
+ // Build valid snapshot then corrupt string length to be huge
705
+ let mut buf = [0u8; 4096];
706
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
707
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
708
+ let total = b.finish();
709
+
710
+ // Corrupt name_length to huge value
711
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
712
+ let item_off = u32::from_ne_bytes(
713
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
714
+ .try_into()
715
+ .unwrap(),
716
+ ) as usize;
717
+ let item_start = dir_end + item_off;
718
+
719
+ buf[item_start + 20..item_start + 24].copy_from_slice(&99999u32.to_ne_bytes());
720
+
721
+ // Re-decode after corruption
722
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
723
+ assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
724
+}
725
+
726
+#[test]
727
+fn cgroups_builder_overflow() {
728
+ let mut buf = [0u8; 64]; // too small for any real item
729
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 0);
730
+ let long_name = [b'A'; 200];
731
+ assert_eq!(b.add(1, 0, 1, &long_name, b""), Err(NipcError::Overflow));
732
+}
733
+
734
+#[test]
735
+fn cgroups_builder_max_items_exceeded() {
736
+ let mut buf = [0u8; 4096];
737
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 0);
738
+ b.add(1, 0, 1, b"a", b"b").unwrap();
739
+ assert_eq!(b.add(2, 0, 1, b"c", b"d"), Err(NipcError::Overflow));
740
+}
741
+
742
+#[test]
743
+fn cgroups_builder_compaction() {
744
+ let mut buf = [0u8; 4096];
745
+ // Reserve 10 directory slots but only add 2 items
746
+ let mut b = CgroupsBuilder::new(&mut buf, 10, 1, 77);
747
+
748
+ b.add(10, 0, 1, b"slice-a", b"/cgroup/slice-a").unwrap();
749
+ b.add(20, 0, 0, b"slice-b", b"/cgroup/slice-b").unwrap();
750
+
751
+ let total = b.finish();
752
+
753
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
754
+ assert_eq!(view.item_count, 2);
755
+ assert_eq!(view.generation, 77);
756
+
757
+ let item = view.item(0).unwrap();
758
+ assert_eq!(item.hash, 10);
759
+ assert_eq!(item.name.as_bytes(), b"slice-a");
760
+
761
+ let item = view.item(1).unwrap();
762
+ assert_eq!(item.hash, 20);
763
+ assert_eq!(item.name.as_bytes(), b"slice-b");
764
+}
765
+
766
+// -----------------------------------------------------------------------
767
+// Alignment utility test
768
+// -----------------------------------------------------------------------
769
+
770
+#[test]
771
+fn test_align8() {
772
+ assert_eq!(align8(0), 0);
773
+ assert_eq!(align8(1), 8);
774
+ assert_eq!(align8(7), 8);
775
+ assert_eq!(align8(8), 8);
776
+ assert_eq!(align8(9), 16);
777
+ assert_eq!(align8(16), 16);
778
+ assert_eq!(align8(17), 24);
779
+}
780
+
781
+// -----------------------------------------------------------------------
782
+// Cross-language wire compatibility: C-Rust byte identity
783
+//
784
+// These tests encode in Rust and verify the exact bytes match what the
785
+// C implementation produces for the same inputs. This ensures identical
786
+// wire output across languages.
787
+// -----------------------------------------------------------------------
788
+
789
+#[test]
790
+fn c_rust_header_bytes_identical() {
791
+ // Encode in Rust
792
+ let h = Header {
793
+ magic: MAGIC_MSG,
794
+ version: VERSION,
795
+ header_len: HEADER_LEN,
796
+ kind: KIND_REQUEST,
797
+ flags: FLAG_BATCH,
798
+ code: METHOD_CGROUPS_SNAPSHOT,
799
+ transport_status: STATUS_OK,
800
+ payload_len: 12345,
801
+ item_count: 42,
802
+ message_id: 0xDEAD_BEEF_CAFE_BABE,
803
+ };
804
+ let mut rust_buf = [0u8; 32];
805
+ h.encode(&mut rust_buf);
806
+
807
+ // Known LE bytes for this header
808
+ let expected: [u8; 32] = [
809
+ 0x43, 0x50, 0x49, 0x4e, // magic
810
+ 0x01, 0x00, // version
811
+ 0x20, 0x00, // header_len
812
+ 0x01, 0x00, // kind
813
+ 0x01, 0x00, // flags
814
+ 0x02, 0x00, // code
815
+ 0x00, 0x00, // transport_status
816
+ 0x39, 0x30, 0x00, 0x00, // payload_len = 12345
817
+ 0x2a, 0x00, 0x00, 0x00, // item_count = 42
818
+ 0xbe, 0xba, 0xfe, 0xca, 0xef, 0xbe, 0xad, 0xde, // message_id
819
+ ];
820
+ assert_eq!(rust_buf, expected);
821
+}
822
+
823
+#[test]
824
+fn c_rust_chunk_bytes_identical() {
825
+ let c = ChunkHeader {
826
+ magic: MAGIC_CHUNK,
827
+ version: VERSION,
828
+ flags: 0,
829
+ message_id: 1,
830
+ total_message_len: 256,
831
+ chunk_index: 1,
832
+ chunk_count: 3,
833
+ chunk_payload_len: 100,
834
+ };
835
+ let mut rust_buf = [0u8; 32];
836
+ c.encode(&mut rust_buf);
837
+
838
+ let expected: [u8; 32] = [
839
+ 0x4b, 0x48, 0x43, 0x4e, // magic
840
+ 0x01, 0x00, // version
841
+ 0x00, 0x00, // flags
842
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // message_id
843
+ 0x00, 0x01, 0x00, 0x00, // total_message_len = 256
844
+ 0x01, 0x00, 0x00, 0x00, // chunk_index
845
+ 0x03, 0x00, 0x00, 0x00, // chunk_count
846
+ 0x64, 0x00, 0x00, 0x00, // chunk_payload_len = 100
847
+ ];
848
+ assert_eq!(rust_buf, expected);
849
+}
850
+
851
+#[test]
852
+fn c_rust_hello_bytes_identical() {
853
+ let h = Hello {
854
+ layout_version: 1,
855
+ flags: 0,
856
+ supported_profiles: PROFILE_BASELINE | PROFILE_SHM_FUTEX,
857
+ preferred_profiles: PROFILE_SHM_FUTEX,
858
+ max_request_payload_bytes: 4096,
859
+ max_request_batch_items: 100,
860
+ max_response_payload_bytes: 1048576,
861
+ max_response_batch_items: 1,
862
+ auth_token: 0xAABB_CCDD_EEFF_0011,
863
+ packet_size: 65536,
864
+ };
865
+
866
+ let mut rust_buf = [0u8; 44];
867
+ h.encode(&mut rust_buf);
868
+
869
+ // Verify key byte positions
870
+ assert_eq!(&rust_buf[0..2], &[0x01, 0x00]); // layout_version
871
+ assert_eq!(&rust_buf[2..4], &[0x00, 0x00]); // flags
872
+ assert_eq!(&rust_buf[4..8], &[0x05, 0x00, 0x00, 0x00]); // supported = 0x05
873
+ assert_eq!(&rust_buf[8..12], &[0x04, 0x00, 0x00, 0x00]); // preferred = 0x04
874
+ assert_eq!(&rust_buf[28..32], &[0x00, 0x00, 0x00, 0x00]); // padding = 0
875
+ assert_eq!(
876
+ &rust_buf[32..40],
877
+ &[0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA]
878
+ ); // auth_token
879
+
880
+ // Round-trip
881
+ let out = Hello::decode(&rust_buf).unwrap();
882
+ assert_eq!(out, h);
883
+}
884
+
885
+#[test]
886
+fn c_rust_hello_ack_bytes_identical() {
887
+ let h = HelloAck {
888
+ layout_version: 1,
889
+ flags: 0,
890
+ server_supported_profiles: 0x07,
891
+ intersection_profiles: 0x05,
892
+ selected_profile: PROFILE_SHM_FUTEX,
893
+ agreed_max_request_payload_bytes: 2048,
894
+ agreed_max_request_batch_items: 50,
895
+ agreed_max_response_payload_bytes: 65536,
896
+ agreed_max_response_batch_items: 1,
897
+ agreed_packet_size: 32768,
898
+ session_id: 0x0000_0001_0000_0007,
899
+ };
900
+ let mut rust_buf = [0u8; 48];
901
+ h.encode(&mut rust_buf);
902
+
903
+ assert_eq!(&rust_buf[0..2], &[0x01, 0x00]);
904
+ assert_eq!(&rust_buf[4..8], &[0x07, 0x00, 0x00, 0x00]); // server_supported
905
+ assert_eq!(&rust_buf[12..16], &[0x04, 0x00, 0x00, 0x00]); // selected = SHM_FUTEX
906
+ assert_eq!(&rust_buf[36..40], &[0x00, 0x00, 0x00, 0x00]); // padding
907
+ assert_eq!(
908
+ &rust_buf[40..48],
909
+ &[0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]
910
+ ); // session_id LE
911
+
912
+ let out = HelloAck::decode(&rust_buf).unwrap();
913
+ assert_eq!(out, h);
914
+}
915
+
916
+#[test]
917
+fn c_rust_cgroups_req_bytes_identical() {
918
+ let r = CgroupsRequest {
919
+ layout_version: 1,
920
+ flags: 0,
921
+ };
922
+ let mut rust_buf = [0u8; 4];
923
+ r.encode(&mut rust_buf);
924
+
925
+ assert_eq!(rust_buf, [0x01, 0x00, 0x00, 0x00]);
926
+
927
+ let out = CgroupsRequest::decode(&rust_buf).unwrap();
928
+ assert_eq!(out, r);
929
+}
930
+
931
+#[test]
932
+fn c_rust_cgroups_snapshot_bytes_identical() {
933
+ // Build a snapshot with the exact same inputs as the C test
934
+ let mut buf = [0u8; 4096];
935
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 100);
936
+ b.add(
937
+ 12345,
938
+ 0x01,
939
+ 1,
940
+ b"docker-abc123",
941
+ b"/sys/fs/cgroup/docker/abc123",
942
+ )
943
+ .unwrap();
944
+ let total = b.finish();
945
+
946
+ // Verify the snapshot header bytes
947
+ assert_eq!(&buf[0..2], &[0x01, 0x00]); // layout_version
948
+ assert_eq!(&buf[2..4], &[0x00, 0x00]); // flags
949
+ assert_eq!(&buf[4..8], &[0x01, 0x00, 0x00, 0x00]); // item_count
950
+ assert_eq!(&buf[8..12], &[0x00, 0x00, 0x00, 0x00]); // systemd_enabled
951
+ assert_eq!(&buf[12..16], &[0x00, 0x00, 0x00, 0x00]); // reserved
952
+ assert_eq!(
953
+ &buf[16..24],
954
+ &[0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
955
+ ); // generation
956
+
957
+ // Verify it decodes correctly
958
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
959
+ assert_eq!(view.item_count, 1);
960
+ assert_eq!(view.generation, 100);
961
+
962
+ let item = view.item(0).unwrap();
963
+ assert_eq!(item.hash, 12345);
964
+ assert_eq!(item.name.as_bytes(), b"docker-abc123");
965
+ assert_eq!(item.path.as_bytes(), b"/sys/fs/cgroup/docker/abc123");
966
+}
967
+
968
+#[test]
969
+fn cgroups_resp_dir_bad_alignment() {
970
+ // Dir entry with unaligned offset
971
+ let mut buf = [0u8; 128];
972
+ buf[0..2].copy_from_slice(&1u16.to_ne_bytes());
973
+ buf[4..8].copy_from_slice(&1u32.to_ne_bytes());
974
+ // offset=3 (not 8-byte aligned), length=32
975
+ buf[24..28].copy_from_slice(&3u32.to_ne_bytes());
976
+ buf[28..32].copy_from_slice(&32u32.to_ne_bytes());
977
+ assert_eq!(
978
+ CgroupsResponseView::decode(&buf).unwrap_err(),
979
+ NipcError::BadAlignment
980
+ );
981
+}
982
+
983
+#[test]
984
+fn cgroups_resp_item_bad_layout_version() {
985
+ // Build valid snapshot, then corrupt the item's layout_version
986
+ let mut buf = [0u8; 4096];
987
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
988
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
989
+ let total = b.finish();
990
+
991
+ // Corrupt item layout_version
992
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
993
+ let item_off = u32::from_ne_bytes(
994
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
995
+ .try_into()
996
+ .unwrap(),
997
+ ) as usize;
998
+ let item_start = dir_end + item_off;
999
+ buf[item_start..item_start + 2].copy_from_slice(&99u16.to_ne_bytes());
1000
+
1001
+ // Re-decode after corruption
1002
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1003
+ assert_eq!(view.item(0).unwrap_err(), NipcError::BadLayout);
1004
+}
1005
+
1006
+#[test]
1007
+fn cgroups_resp_item_name_off_below_header() {
1008
+ // Build valid snapshot, then set name_offset < 32
1009
+ let mut buf = [0u8; 4096];
1010
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1011
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1012
+ let total = b.finish();
1013
+
1014
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1015
+ let item_off = u32::from_ne_bytes(
1016
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1017
+ .try_into()
1018
+ .unwrap(),
1019
+ ) as usize;
1020
+ let item_start = dir_end + item_off;
1021
+ // Set name_offset to 0 (below header)
1022
+ buf[item_start + 16..item_start + 20].copy_from_slice(&0u32.to_ne_bytes());
1023
+
1024
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1025
+ assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
1026
+}
1027
+
1028
+#[test]
1029
+fn cgroups_resp_item_path_off_below_header() {
1030
+ let mut buf = [0u8; 4096];
1031
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1032
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1033
+ let total = b.finish();
1034
+
1035
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1036
+ let item_off = u32::from_ne_bytes(
1037
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1038
+ .try_into()
1039
+ .unwrap(),
1040
+ ) as usize;
1041
+ let item_start = dir_end + item_off;
1042
+ // Set path_offset to 16 (below header)
1043
+ buf[item_start + 24..item_start + 28].copy_from_slice(&16u32.to_ne_bytes());
1044
+
1045
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1046
+ assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
1047
+}
1048
+
1049
+#[test]
1050
+fn cgroups_resp_item_path_missing_nul() {
1051
+ let mut buf = [0u8; 4096];
1052
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1053
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1054
+ let total = b.finish();
1055
+
1056
+ // Corrupt path NUL
1057
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1058
+ let item_off = u32::from_ne_bytes(
1059
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1060
+ .try_into()
1061
+ .unwrap(),
1062
+ ) as usize;
1063
+ let item_start = dir_end + item_off;
1064
+ let poff =
1065
+ u32::from_ne_bytes(buf[item_start + 24..item_start + 28].try_into().unwrap()) as usize;
1066
+ let plen =
1067
+ u32::from_ne_bytes(buf[item_start + 28..item_start + 32].try_into().unwrap()) as usize;
1068
+ buf[item_start + poff + plen] = b'X';
1069
+
1070
+ // Re-decode after corruption
1071
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1072
+ assert_eq!(view.item(0).unwrap_err(), NipcError::MissingNul);
1073
+}
1074
+
1075
+#[test]
1076
+fn cgroups_resp_item_overlap_rejected() {
1077
+ // Build a valid item, then manually set path_offset to overlap with name
1078
+ let mut buf = [0u8; 4096];
1079
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1080
+ b.add(1, 0, 1, b"hello", b"/path").unwrap();
1081
+ let total = b.finish();
1082
+
1083
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1084
+ let item_off = u32::from_ne_bytes(
1085
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1086
+ .try_into()
1087
+ .unwrap(),
1088
+ ) as usize;
1089
+ let item_start = dir_end + item_off;
1090
+
1091
+ // name_off=32, name_len=5, so name region is [32..38)
1092
+ // Set path_off=34 (inside name region), path_len=1
1093
+ buf[item_start + 24..item_start + 28].copy_from_slice(&34u32.to_ne_bytes());
1094
+ buf[item_start + 28..item_start + 32].copy_from_slice(&1u32.to_ne_bytes());
1095
+ // Ensure NUL at item[34+1]=item[35]
1096
+ buf[item_start + 35] = 0;
1097
+
1098
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1099
+ assert_eq!(view.item(0).unwrap_err(), NipcError::BadLayout);
1100
+}
1101
+
1102
+// -------------------------------------------------------------------
1103
+// Proptest: fuzz / property-based tests for all decode paths
1104
+// -------------------------------------------------------------------
1105
+
1106
+mod proptests {
1107
+ use super::*;
1108
+ use proptest::prelude::*;
1109
+
1110
+ // Arbitrary bytes -- no decode path may panic on any input.
1111
+
1112
+ proptest! {
1113
+ #[test]
1114
+ fn decode_header_never_panics(data: Vec<u8>) {
1115
+ let _ = Header::decode(&data);
1116
+ }
1117
+
1118
+ #[test]
1119
+ fn decode_chunk_header_never_panics(data: Vec<u8>) {
1120
+ let _ = ChunkHeader::decode(&data);
1121
+ }
1122
+
1123
+ #[test]
1124
+ fn decode_hello_never_panics(data: Vec<u8>) {
1125
+ let _ = Hello::decode(&data);
1126
+ }
1127
+
1128
+ #[test]
1129
+ fn decode_hello_ack_never_panics(data: Vec<u8>) {
1130
+ let _ = HelloAck::decode(&data);
1131
+ }
1132
+
1133
+ #[test]
1134
+ fn decode_cgroups_request_never_panics(data: Vec<u8>) {
1135
+ let _ = CgroupsRequest::decode(&data);
1136
+ }
1137
+
1138
+ #[test]
1139
+ fn decode_cgroups_response_never_panics(data: Vec<u8>) {
1140
+ let result = CgroupsResponseView::decode(&data);
1141
+ if let Ok(view) = result {
1142
+ // Exercise item access on valid decodes.
1143
+ let limit = view.item_count.min(64);
1144
+ for i in 0..limit {
1145
+ let _ = view.item(i);
1146
+ }
1147
+ // Out-of-bounds must not panic.
1148
+ let _ = view.item(view.item_count);
1149
+ }
1150
+ }
1151
+
1152
+ #[test]
1153
+ fn decode_cgroups_lookup_request_never_panics(data: Vec<u8>) {
1154
+ let result = CgroupsLookupRequestView::decode(&data);
1155
+ if let Ok(view) = result {
1156
+ let limit = view.item_count.min(64);
1157
+ for i in 0..limit {
1158
+ let _ = view.item(i);
1159
+ }
1160
+ let _ = view.item(view.item_count);
1161
+ }
1162
+ }
1163
+
1164
+ #[test]
1165
+ fn decode_cgroups_lookup_response_never_panics(data: Vec<u8>) {
1166
+ let result = CgroupsLookupResponseView::decode(&data);
1167
+ if let Ok(view) = result {
1168
+ let limit = view.item_count.min(64);
1169
+ for i in 0..limit {
1170
+ if let Ok(item) = view.item(i) {
1171
+ let label_limit = item.label_count.min(64);
1172
+ for j in 0..label_limit {
1173
+ let _ = item.label(j.into());
1174
+ }
1175
+ let _ = item.label(item.label_count.into());
1176
+ }
1177
+ }
1178
+ let _ = view.item(view.item_count);
1179
+ }
1180
+ }
1181
+
1182
+ #[test]
1183
+ fn decode_apps_lookup_request_never_panics(data: Vec<u8>) {
1184
+ let result = AppsLookupRequestView::decode(&data);
1185
+ if let Ok(view) = result {
1186
+ let limit = view.item_count.min(64);
1187
+ for i in 0..limit {
1188
+ let _ = view.item(i);
1189
+ }
1190
+ let _ = view.item(view.item_count);
1191
+ }
1192
+ }
1193
+
1194
+ #[test]
1195
+ fn decode_apps_lookup_response_never_panics(data: Vec<u8>) {
1196
+ let result = AppsLookupResponseView::decode(&data);
1197
+ if let Ok(view) = result {
1198
+ let limit = view.item_count.min(64);
1199
+ for i in 0..limit {
1200
+ if let Ok(item) = view.item(i) {
1201
+ let label_limit = item.label_count.min(64);
1202
+ for j in 0..label_limit {
1203
+ let _ = item.label(j.into());
1204
+ }
1205
+ let _ = item.label(item.label_count.into());
1206
+ }
1207
+ }
1208
+ let _ = view.item(view.item_count);
1209
+ }
1210
+ }
1211
+
1212
+ #[test]
1213
+ fn batch_dir_decode_never_panics(
1214
+ data: Vec<u8>,
1215
+ item_count in 0u32..128,
1216
+ packed_area_len in 0u32..65536,
1217
+ ) {
1218
+ let _ = batch_dir_decode(&data, item_count, packed_area_len);
1219
+ }
1220
+
1221
+ #[test]
1222
+ fn batch_item_get_never_panics(
1223
+ data: Vec<u8>,
1224
+ item_count in 0u32..128,
1225
+ index in 0u32..128,
1226
+ ) {
1227
+ let _ = batch_item_get(&data, item_count, index);
1228
+ }
1229
+ }
1230
+
1231
+ // Roundtrip tests: encode random valid values, decode, verify match.
1232
+
1233
+ proptest! {
1234
+ #[test]
1235
+ fn encode_decode_header_roundtrip(
1236
+ kind in 1u16..=3,
1237
+ flags in any::<u16>(),
1238
+ code in any::<u16>(),
1239
+ transport_status in any::<u16>(),
1240
+ payload_len in any::<u32>(),
1241
+ item_count in any::<u32>(),
1242
+ message_id in any::<u64>(),
1243
+ ) {
1244
+ let h = Header {
1245
+ magic: MAGIC_MSG,
1246
+ version: VERSION,
1247
+ header_len: HEADER_LEN,
1248
+ kind,
1249
+ flags,
1250
+ code,
1251
+ transport_status,
1252
+ payload_len,
1253
+ item_count,
1254
+ message_id,
1255
+ };
1256
+ let mut buf = [0u8; 64];
1257
+ let n = h.encode(&mut buf);
1258
+ prop_assert_eq!(n, HEADER_SIZE);
1259
+ let decoded = Header::decode(&buf[..n]).unwrap();
1260
+ prop_assert_eq!(decoded, h);
1261
+ }
1262
+
1263
+ #[test]
1264
+ fn encode_decode_hello_roundtrip(
1265
+ supported in any::<u32>(),
1266
+ preferred in any::<u32>(),
1267
+ max_req_payload in any::<u32>(),
1268
+ max_req_batch in any::<u32>(),
1269
+ max_resp_payload in any::<u32>(),
1270
+ max_resp_batch in any::<u32>(),
1271
+ auth_token in any::<u64>(),
1272
+ packet_size in any::<u32>(),
1273
+ ) {
1274
+ let h = Hello {
1275
+ layout_version: 1,
1276
+ flags: 0,
1277
+ supported_profiles: supported,
1278
+ preferred_profiles: preferred,
1279
+ max_request_payload_bytes: max_req_payload,
1280
+ max_request_batch_items: max_req_batch,
1281
+ max_response_payload_bytes: max_resp_payload,
1282
+ max_response_batch_items: max_resp_batch,
1283
+ auth_token,
1284
+ packet_size,
1285
+ };
1286
+ let mut buf = [0u8; 64];
1287
+ let n = h.encode(&mut buf);
1288
+ prop_assert_eq!(n, HELLO_SIZE);
1289
+ let decoded = Hello::decode(&buf[..n]).unwrap();
1290
+ prop_assert_eq!(decoded, h);
1291
+ }
1292
+ }
1293
+}
1294
+
1295
+// -----------------------------------------------------------------------
1296
+// NipcError Display coverage
1297
+// -----------------------------------------------------------------------
1298
+
1299
+#[test]
1300
+fn nipc_error_display_all_variants() {
1301
+ // Exercise the Display impl for every NipcError variant (lines 101-113)
1302
+ let cases: Vec<(NipcError, &str)> = vec![
1303
+ (NipcError::Truncated, "buffer too short"),
1304
+ (NipcError::BadMagic, "magic value mismatch"),
1305
+ (NipcError::BadVersion, "unsupported version"),
1306
+ (NipcError::BadHeaderLen, "header_len != 32"),
1307
+ (NipcError::BadKind, "unknown message kind"),
1308
+ (NipcError::BadLayout, "unknown layout_version"),
1309
+ (NipcError::OutOfBounds, "offset+length exceeds data"),
1310
+ (NipcError::MissingNul, "string not NUL-terminated"),
1311
+ (NipcError::BadAlignment, "item not 8-byte aligned"),
1312
+ (NipcError::BadItemCount, "item count inconsistent"),
1313
+ (NipcError::Overflow, "builder out of space"),
1314
+ ];
1315
+ for (err, expected) in cases {
1316
+ let msg = format!("{}", err);
1317
+ assert_eq!(msg, expected, "Display for {:?}", err);
1318
+ }
1319
+ // Also verify std::error::Error is implemented
1320
+ let err: &dyn std::error::Error = &NipcError::Truncated;
1321
+ let _ = format!("{err}");
1322
+}
1323
+
1324
+// -----------------------------------------------------------------------
1325
+// ChunkHeader decode: flags != 0 and chunk_payload_len == 0
1326
+// -----------------------------------------------------------------------
1327
+
1328
+#[test]
1329
+fn chunk_decode_bad_flags() {
1330
+ // Line 257: flags != 0 -> BadLayout
1331
+ let c = ChunkHeader {
1332
+ magic: MAGIC_CHUNK,
1333
+ version: VERSION,
1334
+ flags: 0x01, // non-zero flags
1335
+ message_id: 1,
1336
+ total_message_len: 100,
1337
+ chunk_index: 0,
1338
+ chunk_count: 1,
1339
+ chunk_payload_len: 50,
1340
+ };
1341
+ let mut buf = [0u8; 32];
1342
+ c.encode(&mut buf);
1343
+ assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::BadLayout));
1344
+}
1345
+
1346
+#[test]
1347
+fn chunk_decode_zero_payload_len() {
1348
+ // Line 260: chunk_payload_len == 0 -> BadLayout
1349
+ let c = ChunkHeader {
1350
+ magic: MAGIC_CHUNK,
1351
+ version: VERSION,
1352
+ flags: 0,
1353
+ message_id: 1,
1354
+ total_message_len: 100,
1355
+ chunk_index: 0,
1356
+ chunk_count: 1,
1357
+ chunk_payload_len: 0,
1358
+ };
1359
+ let mut buf = [0u8; 32];
1360
+ c.encode(&mut buf);
1361
+ assert_eq!(ChunkHeader::decode(&buf), Err(NipcError::BadLayout));
1362
+}
1363
+
1364
+// -----------------------------------------------------------------------
1365
+// batch_dir_encode: buffer too small (line 282)
1366
+// -----------------------------------------------------------------------
1367
+
1368
+#[test]
1369
+fn batch_dir_encode_too_small() {
1370
+ let entries = [
1371
+ BatchEntry {
1372
+ offset: 0,
1373
+ length: 8,
1374
+ },
1375
+ BatchEntry {
1376
+ offset: 8,
1377
+ length: 8,
1378
+ },
1379
+ ];
1380
+ let mut buf = [0u8; 12]; // needs 16, only 12
1381
+ assert_eq!(batch_dir_encode(&entries, &mut buf), 0);
1382
+}
1383
+
1384
+// -----------------------------------------------------------------------
1385
+// batch_dir_validate error paths (lines 329, 336, 339)
1386
+// -----------------------------------------------------------------------
1387
+
1388
+#[test]
1389
+fn batch_dir_validate_truncated() {
1390
+ let buf = [0u8; 4]; // too short for 1 entry (needs 8)
1391
+ assert_eq!(batch_dir_validate(&buf, 1, 100), Err(NipcError::Truncated));
1392
+}
1393
+
1394
+#[test]
1395
+fn batch_dir_validate_bad_alignment() {
1396
+ let mut buf = [0u8; 8];
1397
+ buf[0..4].copy_from_slice(&3u32.to_ne_bytes()); // unaligned offset
1398
+ buf[4..8].copy_from_slice(&8u32.to_ne_bytes());
1399
+ assert_eq!(
1400
+ batch_dir_validate(&buf, 1, 100),
1401
+ Err(NipcError::BadAlignment)
1402
+ );
1403
+}
1404
+
1405
+#[test]
1406
+fn batch_dir_validate_out_of_bounds() {
1407
+ let mut buf = [0u8; 8];
1408
+ buf[0..4].copy_from_slice(&0u32.to_ne_bytes());
1409
+ buf[4..8].copy_from_slice(&200u32.to_ne_bytes()); // exceeds packed_area_len
1410
+ assert_eq!(
1411
+ batch_dir_validate(&buf, 1, 100),
1412
+ Err(NipcError::OutOfBounds)
1413
+ );
1414
+}
1415
+
1416
+#[test]
1417
+fn batch_dir_validate_ok() {
1418
+ let mut buf = [0u8; 16];
1419
+ buf[0..4].copy_from_slice(&0u32.to_ne_bytes());
1420
+ buf[4..8].copy_from_slice(&8u32.to_ne_bytes());
1421
+ buf[8..12].copy_from_slice(&8u32.to_ne_bytes());
1422
+ buf[12..16].copy_from_slice(&8u32.to_ne_bytes());
1423
+ assert!(batch_dir_validate(&buf, 2, 100).is_ok());
1424
+}
1425
+
1426
+// -----------------------------------------------------------------------
1427
+// batch_item_get: alignment check (line 375)
1428
+// -----------------------------------------------------------------------
1429
+
1430
+#[test]
1431
+fn batch_item_get_bad_alignment() {
1432
+ // Manually craft a batch payload with unaligned offset
1433
+ let mut buf = [0u8; 64];
1434
+ // Directory: 1 entry at offset 0 of buf
1435
+ buf[0..4].copy_from_slice(&3u32.to_ne_bytes()); // unaligned offset
1436
+ buf[4..8].copy_from_slice(&4u32.to_ne_bytes());
1437
+ assert_eq!(batch_item_get(&buf, 1, 0), Err(NipcError::BadAlignment));
1438
+}
1439
+
1440
+#[test]
1441
+fn batch_item_get_truncated_dir() {
1442
+ // Payload too small to hold the directory
1443
+ let buf = [0u8; 4]; // needs at least 8 for 1 item directory
1444
+ assert_eq!(batch_item_get(&buf, 1, 0), Err(NipcError::Truncated));
1445
+}
1446
+
1447
+// -----------------------------------------------------------------------
1448
+// BatchBuilder::finish compaction (lines 451-456)
1449
+// -----------------------------------------------------------------------
1450
+
1451
+#[test]
1452
+fn batch_builder_compaction() {
1453
+ // Reserve space for 8 items but add only 2 -- triggers compaction
1454
+ let mut buf = [0u8; 1024];
1455
+ let mut b = BatchBuilder::new(&mut buf, 8);
1456
+
1457
+ let item1 = [1u8, 2, 3, 4, 5, 6, 7, 8];
1458
+ let item2 = [10u8, 20, 30, 40];
1459
+
1460
+ b.add(&item1).unwrap();
1461
+ b.add(&item2).unwrap();
1462
+
1463
+ // dir_end for 8 items = align8(8*8) = 64
1464
+ // final_dir_aligned for 2 items = align8(2*8) = 16
1465
+ // This triggers the copy_within compaction branch (line 453-456)
1466
+ let (total, count) = b.finish();
1467
+ assert_eq!(count, 2);
1468
+ assert!(total > 0);
1469
+
1470
+ // Verify the items can still be extracted correctly
1471
+ let (data, len) = batch_item_get(&buf[..total], 2, 0).unwrap();
1472
+ assert_eq!(len as usize, item1.len());
1473
+ assert_eq!(data, &item1);
1474
+
1475
+ let (data, len) = batch_item_get(&buf[..total], 2, 1).unwrap();
1476
+ assert_eq!(len as usize, item2.len());
1477
+ assert_eq!(data, &item2);
1478
+}
1479
+
1480
+// -----------------------------------------------------------------------
1481
+// HelloAck decode: flags != 0 (line 606)
1482
+// -----------------------------------------------------------------------
1483
+
1484
+#[test]
1485
+fn hello_ack_decode_bad_flags() {
1486
+ let h = HelloAck {
1487
+ layout_version: 1,
1488
+ flags: 1, // non-zero flags
1489
+ ..Default::default()
1490
+ };
1491
+ let mut buf = [0u8; 48];
1492
+ h.encode(&mut buf);
1493
+ assert_eq!(HelloAck::decode(&buf), Err(NipcError::BadLayout));
1494
+}
1495
+
1496
+// -----------------------------------------------------------------------
1497
+// Hello decode: non-zero padding (line 527)
1498
+// -----------------------------------------------------------------------
1499
+
1500
+#[test]
1501
+fn hello_decode_bad_padding() {
1502
+ let h = Hello {
1503
+ layout_version: 1,
1504
+ flags: 0,
1505
+ ..Default::default()
1506
+ };
1507
+ let mut buf = [0u8; 44];
1508
+ h.encode(&mut buf);
1509
+ // Corrupt the padding bytes at 28..32
1510
+ buf[28..32].copy_from_slice(&1u32.to_ne_bytes());
1511
+ assert_eq!(Hello::decode(&buf), Err(NipcError::BadLayout));
1512
+}
1513
+
1514
+// -----------------------------------------------------------------------
1515
+// Cgroups request: non-zero flags (line 45)
1516
+// -----------------------------------------------------------------------
1517
+
1518
+#[test]
1519
+fn cgroups_req_decode_bad_flags() {
1520
+ let r = CgroupsRequest {
1521
+ layout_version: 1,
1522
+ flags: 1, // non-zero flags -> BadLayout
1523
+ };
1524
+ let mut buf = [0u8; 4];
1525
+ r.encode(&mut buf);
1526
+ assert_eq!(CgroupsRequest::decode(&buf), Err(NipcError::BadLayout));
1527
+}
1528
+
1529
+// -----------------------------------------------------------------------
1530
+// CgroupsResponseView: non-zero flags and reserved (lines 125, 130)
1531
+// -----------------------------------------------------------------------
1532
+
1533
+#[test]
1534
+fn cgroups_resp_decode_bad_flags() {
1535
+ // Line 125: flags != 0 -> BadLayout
1536
+ let mut buf = [0u8; 24];
1537
+ buf[0..2].copy_from_slice(&1u16.to_ne_bytes()); // layout_version = 1
1538
+ buf[2..4].copy_from_slice(&1u16.to_ne_bytes()); // flags = 1 (non-zero)
1539
+ assert_eq!(
1540
+ CgroupsResponseView::decode(&buf).unwrap_err(),
1541
+ NipcError::BadLayout
1542
+ );
1543
+}
1544
+
1545
+#[test]
1546
+fn cgroups_resp_decode_bad_reserved() {
1547
+ // Line 130: reserved != 0 -> BadLayout
1548
+ let mut buf = [0u8; 24];
1549
+ buf[0..2].copy_from_slice(&1u16.to_ne_bytes()); // layout_version = 1
1550
+ buf[2..4].copy_from_slice(&0u16.to_ne_bytes()); // flags = 0
1551
+ buf[12..16].copy_from_slice(&1u32.to_ne_bytes()); // reserved = 1
1552
+ assert_eq!(
1553
+ CgroupsResponseView::decode(&buf).unwrap_err(),
1554
+ NipcError::BadLayout
1555
+ );
1556
+}
1557
+
1558
+// -----------------------------------------------------------------------
1559
+// CgroupsResponseView: bad alignment in directory (line 149)
1560
+// -----------------------------------------------------------------------
1561
+
1562
+#[test]
1563
+fn cgroups_resp_decode_bad_dir_alignment() {
1564
+ // dir entry with offset not aligned to 8
1565
+ let mut buf = [0u8; 128];
1566
+ buf[0..2].copy_from_slice(&1u16.to_ne_bytes()); // layout_version
1567
+ buf[4..8].copy_from_slice(&1u32.to_ne_bytes()); // item_count = 1
1568
+ // Dir entry at offset 24: offset=3 (unaligned), length=32
1569
+ buf[24..28].copy_from_slice(&3u32.to_ne_bytes());
1570
+ buf[28..32].copy_from_slice(&32u32.to_ne_bytes());
1571
+ assert_eq!(
1572
+ CgroupsResponseView::decode(&buf).unwrap_err(),
1573
+ NipcError::BadAlignment
1574
+ );
1575
+}
1576
+
1577
+// -----------------------------------------------------------------------
1578
+// CgroupsItemView: bad layout_version, bad flags (lines 200-206)
1579
+// -----------------------------------------------------------------------
1580
+
1581
+#[test]
1582
+fn cgroups_item_bad_layout_version() {
1583
+ // Build valid snapshot then corrupt item layout_version
1584
+ let mut buf = [0u8; 4096];
1585
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1586
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1587
+ let total = b.finish();
1588
+
1589
+ // Find item start
1590
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1591
+ let item_off = u32::from_ne_bytes(
1592
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1593
+ .try_into()
1594
+ .unwrap(),
1595
+ ) as usize;
1596
+ let item_start = dir_end + item_off;
1597
+
1598
+ // Corrupt layout_version to 99
1599
+ buf[item_start..item_start + 2].copy_from_slice(&99u16.to_ne_bytes());
1600
+
1601
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1602
+ assert_eq!(view.item(0).unwrap_err(), NipcError::BadLayout);
1603
+}
1604
+
1605
+#[test]
1606
+fn cgroups_item_bad_flags() {
1607
+ // Build valid snapshot then corrupt item flags
1608
+ let mut buf = [0u8; 4096];
1609
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1610
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1611
+ let total = b.finish();
1612
+
1613
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1614
+ let item_off = u32::from_ne_bytes(
1615
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1616
+ .try_into()
1617
+ .unwrap(),
1618
+ ) as usize;
1619
+ let item_start = dir_end + item_off;
1620
+
1621
+ // Corrupt flags to non-zero
1622
+ buf[item_start + 2..item_start + 4].copy_from_slice(&1u16.to_ne_bytes());
1623
+
1624
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1625
+ assert_eq!(view.item(0).unwrap_err(), NipcError::BadLayout);
1626
+}
1627
+
1628
+// -----------------------------------------------------------------------
1629
+// CgroupsItemView: name_off < ITEM_HDR_SIZE (line 211)
1630
+// -----------------------------------------------------------------------
1631
+
1632
+#[test]
1633
+fn cgroups_item_name_off_too_small() {
1634
+ let mut buf = [0u8; 4096];
1635
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1636
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1637
+ let total = b.finish();
1638
+
1639
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1640
+ let item_off = u32::from_ne_bytes(
1641
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1642
+ .try_into()
1643
+ .unwrap(),
1644
+ ) as usize;
1645
+ let item_start = dir_end + item_off;
1646
+
1647
+ // Set name_offset to 0 (< 32 = CGROUPS_ITEM_HDR_SIZE)
1648
+ buf[item_start + 16..item_start + 20].copy_from_slice(&0u32.to_ne_bytes());
1649
+
1650
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1651
+ assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
1652
+}
1653
+
1654
+// -----------------------------------------------------------------------
1655
+// CgroupsItemView: path NUL missing (line 228)
1656
+// -----------------------------------------------------------------------
1657
+
1658
+#[test]
1659
+fn cgroups_item_path_missing_nul() {
1660
+ let mut buf = [0u8; 4096];
1661
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1662
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1663
+ let total = b.finish();
1664
+
1665
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1666
+ let item_off = u32::from_ne_bytes(
1667
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1668
+ .try_into()
1669
+ .unwrap(),
1670
+ ) as usize;
1671
+ let item_start = dir_end + item_off;
1672
+
1673
+ // Find the path NUL terminator and corrupt it
1674
+ let path_off =
1675
+ u32::from_ne_bytes(buf[item_start + 24..item_start + 28].try_into().unwrap()) as usize;
1676
+ let path_len =
1677
+ u32::from_ne_bytes(buf[item_start + 28..item_start + 32].try_into().unwrap()) as usize;
1678
+ buf[item_start + path_off + path_len] = b'X'; // corrupt path NUL
1679
+
1680
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1681
+ assert_eq!(view.item(0).unwrap_err(), NipcError::MissingNul);
1682
+}
1683
+
1684
+// -----------------------------------------------------------------------
1685
+// CgroupsItemView: path_off < ITEM_HDR_SIZE (line 222)
1686
+// -----------------------------------------------------------------------
1687
+
1688
+#[test]
1689
+fn cgroups_item_path_off_too_small() {
1690
+ let mut buf = [0u8; 4096];
1691
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1692
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1693
+ let total = b.finish();
1694
+
1695
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1696
+ let item_off = u32::from_ne_bytes(
1697
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1698
+ .try_into()
1699
+ .unwrap(),
1700
+ ) as usize;
1701
+ let item_start = dir_end + item_off;
1702
+
1703
+ // Set path_offset to 0 (< 32 = CGROUPS_ITEM_HDR_SIZE)
1704
+ buf[item_start + 24..item_start + 28].copy_from_slice(&0u32.to_ne_bytes());
1705
+
1706
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1707
+ assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
1708
+}
1709
+
1710
+// -----------------------------------------------------------------------
1711
+// CgroupsItemView: path string OOB (line 225)
1712
+// -----------------------------------------------------------------------
1713
+
1714
+#[test]
1715
+fn cgroups_item_path_string_oob() {
1716
+ let mut buf = [0u8; 4096];
1717
+ let mut b = CgroupsBuilder::new(&mut buf, 1, 0, 1);
1718
+ b.add(1, 0, 1, b"test", b"/test").unwrap();
1719
+ let total = b.finish();
1720
+
1721
+ let dir_end = CGROUPS_RESP_HDR_SIZE + 1 * CGROUPS_DIR_ENTRY_SIZE;
1722
+ let item_off = u32::from_ne_bytes(
1723
+ buf[CGROUPS_RESP_HDR_SIZE..CGROUPS_RESP_HDR_SIZE + 4]
1724
+ .try_into()
1725
+ .unwrap(),
1726
+ ) as usize;
1727
+ let item_start = dir_end + item_off;
1728
+
1729
+ // Corrupt path_length to huge value
1730
+ buf[item_start + 28..item_start + 32].copy_from_slice(&99999u32.to_ne_bytes());
1731
+
1732
+ let view = CgroupsResponseView::decode(&buf[..total]).unwrap();
1733
+ assert_eq!(view.item(0).unwrap_err(), NipcError::OutOfBounds);
1734
+}
1735
+
1736
+// -----------------------------------------------------------------------
1737
+// Cgroups dispatch (lines 438-447)
1738
+// -----------------------------------------------------------------------
1739
+
1740
+#[test]
1741
+fn dispatch_cgroups_snapshot_bad_request() {
1742
+ // Bad request (too short) -> dispatch returns None (line 438)
1743
+ let mut resp = [0u8; 4096];
1744
+ let result =
1745
+ crate::protocol::dispatch_cgroups_snapshot(&[], &mut resp, 1, |_req, _builder| true);
1746
+ assert!(result.is_none());
1747
+}
1748
+
1749
+#[test]
1750
+fn dispatch_cgroups_snapshot_handler_returns_false() {
1751
+ // Handler returns false -> dispatch returns None (lines 440-441)
1752
+ let req = CgroupsRequest {
1753
+ layout_version: 1,
1754
+ flags: 0,
1755
+ };
1756
+ let mut req_buf = [0u8; 4];
1757
+ req.encode(&mut req_buf);
1758
+ let mut resp = [0u8; 4096];
1759
+ let result =
1760
+ crate::protocol::dispatch_cgroups_snapshot(&req_buf, &mut resp, 1, |_req, _builder| false);
1761
+ assert!(result.is_none());
1762
+}
1763
+
1764
+#[test]
1765
+fn dispatch_cgroups_snapshot_success() {
1766
+ let req = CgroupsRequest {
1767
+ layout_version: 1,
1768
+ flags: 0,
1769
+ };
1770
+ let mut req_buf = [0u8; 4];
1771
+ req.encode(&mut req_buf);
1772
+ let mut resp = [0u8; 4096];
1773
+ let result =
1774
+ crate::protocol::dispatch_cgroups_snapshot(&req_buf, &mut resp, 2, |_req, builder| {
1775
+ builder.add(1, 0, 1, b"cg1", b"/test").unwrap();
1776
+ true
1777
+ });
1778
+ assert!(result.is_some());
1779
+ let n = result.unwrap();
1780
+ let view = CgroupsResponseView::decode(&resp[..n]).unwrap();
1781
+ assert_eq!(view.item_count, 1);
1782
+}
1783
+
1784
+// -----------------------------------------------------------------------
1785
+// Dispatch increment / string_reverse buf overflow (lines 27, 58)
1786
+// -----------------------------------------------------------------------
1787
+
1788
+#[test]
1789
+fn dispatch_increment_resp_too_small() {
1790
+ // Response buffer too small -> encode returns 0 -> dispatch returns None
1791
+ let req_val = 42u64;
1792
+ let mut req_buf = [0u8; 8];
1793
+ crate::protocol::increment_encode(req_val, &mut req_buf);
1794
+ let mut resp = [0u8; 4]; // too small for 8-byte response
1795
+ let result = crate::protocol::dispatch_increment(&req_buf, &mut resp, |v| Some(v + 1));
1796
+ assert!(result.is_none());
1797
+}
1798
+
1799
+#[test]
1800
+fn dispatch_increment_handler_none() {
1801
+ let mut req_buf = [0u8; 8];
1802
+ crate::protocol::increment_encode(42, &mut req_buf);
1803
+ let mut resp = [0u8; 8];
1804
+ let result = crate::protocol::dispatch_increment(&req_buf, &mut resp, |_| None);
1805
+ assert!(result.is_none());
1806
+}
1807
+
1808
+#[test]
1809
+fn dispatch_string_reverse_resp_too_small() {
1810
+ let s = b"hello";
1811
+ let mut req_buf = [0u8; 64];
1812
+ crate::protocol::string_reverse_encode(s, &mut req_buf);
1813
+ let mut resp = [0u8; 4]; // too small
1814
+ let result = crate::protocol::dispatch_string_reverse(&req_buf, &mut resp, |data| {
1815
+ Some(data.iter().rev().copied().collect())
1816
+ });
1817
+ assert!(result.is_none());
1818
+}
1819
+
1820
+#[test]
1821
+fn dispatch_string_reverse_handler_none() {
1822
+ let s = b"hello";
1823
+ let mut req_buf = [0u8; 64];
1824
+ crate::protocol::string_reverse_encode(s, &mut req_buf);
1825
+ let mut resp = [0u8; 64];
1826
+ let result = crate::protocol::dispatch_string_reverse(&req_buf, &mut resp, |_| None);
1827
+ assert!(result.is_none());
1828
+}
src/crates/netipc/src/service/apps_lookup.rs
new
+277
@@ -0,0 +1,277 @@
1
+//! L2 apps-lookup service facade.
2
+
3
+use super::raw;
4
+use crate::protocol::{AppsLookupResponseView, NipcError, METHOD_APPS_LOOKUP, PROFILE_BASELINE};
5
+
6
+#[cfg(unix)]
7
+use crate::transport::posix::{
8
+ ClientConfig as TransportClientConfig, ServerConfig as TransportServerConfig,
9
+};
10
+
11
+#[cfg(windows)]
12
+use crate::transport::windows::{
13
+ ClientConfig as TransportClientConfig, ServerConfig as TransportServerConfig,
14
+};
15
+
16
+use std::sync::atomic::AtomicBool;
17
+use std::sync::Arc;
18
+
19
+pub use raw::{AppsLookupHandler, ClientState, ClientStatus};
20
+
21
+#[derive(Debug, Clone)]
22
+pub struct ClientConfig {
23
+ pub supported_profiles: u32,
24
+ pub preferred_profiles: u32,
25
+ pub max_request_batch_items: u32,
26
+ pub max_response_payload_bytes: u32,
27
+ pub auth_token: u64,
28
+}
29
+
30
+impl Default for ClientConfig {
31
+ fn default() -> Self {
32
+ Self {
33
+ supported_profiles: PROFILE_BASELINE,
34
+ preferred_profiles: 0,
35
+ max_request_batch_items: 0,
36
+ max_response_payload_bytes: 0,
37
+ auth_token: 0,
38
+ }
39
+ }
40
+}
41
+
42
+impl ClientConfig {
43
+ fn into_transport(self) -> TransportClientConfig {
44
+ let mut transport = TransportClientConfig::default();
45
+ transport.supported_profiles = self.supported_profiles;
46
+ transport.preferred_profiles = self.preferred_profiles;
47
+ transport.max_request_batch_items = self.max_request_batch_items;
48
+ transport.max_response_payload_bytes = self.max_response_payload_bytes;
49
+ transport.max_response_batch_items = self.max_request_batch_items;
50
+ transport.auth_token = self.auth_token;
51
+ transport
52
+ }
53
+}
54
+
55
+#[derive(Debug, Clone)]
56
+pub struct ServerConfig {
57
+ pub supported_profiles: u32,
58
+ pub preferred_profiles: u32,
59
+ pub max_request_batch_items: u32,
60
+ pub max_response_payload_bytes: u32,
61
+ pub auth_token: u64,
62
+}
63
+
64
+impl Default for ServerConfig {
65
+ fn default() -> Self {
66
+ Self {
67
+ supported_profiles: PROFILE_BASELINE,
68
+ preferred_profiles: 0,
69
+ max_request_batch_items: 0,
70
+ max_response_payload_bytes: 0,
71
+ auth_token: 0,
72
+ }
73
+ }
74
+}
75
+
76
+impl ServerConfig {
77
+ fn into_transport(self) -> TransportServerConfig {
78
+ let mut transport = TransportServerConfig::default();
79
+ transport.supported_profiles = self.supported_profiles;
80
+ transport.preferred_profiles = self.preferred_profiles;
81
+ transport.max_request_batch_items = self.max_request_batch_items;
82
+ transport.max_response_payload_bytes = self.max_response_payload_bytes;
83
+ transport.max_response_batch_items = self.max_request_batch_items;
84
+ transport.auth_token = self.auth_token;
85
+ transport
86
+ }
87
+}
88
+
89
+pub struct AppsLookupClient {
90
+ inner: raw::RawClient,
91
+}
92
+
93
+impl AppsLookupClient {
94
+ pub fn new(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
95
+ Self {
96
+ inner: raw::RawClient::new_apps_lookup(run_dir, service_name, config.into_transport()),
97
+ }
98
+ }
99
+
100
+ pub fn refresh(&mut self) -> bool {
101
+ self.inner.refresh()
102
+ }
103
+
104
+ pub fn ready(&self) -> bool {
105
+ self.inner.ready()
106
+ }
107
+
108
+ pub fn status(&self) -> ClientStatus {
109
+ self.inner.status()
110
+ }
111
+
112
+ pub fn call(&mut self, pids: &[u32]) -> Result<AppsLookupResponseView<'_>, NipcError> {
113
+ self.inner.call_apps_lookup(pids)
114
+ }
115
+
116
+ pub fn close(&mut self) {
117
+ self.inner.close();
118
+ }
119
+}
120
+
121
+impl Drop for AppsLookupClient {
122
+ fn drop(&mut self) {
123
+ self.close();
124
+ }
125
+}
126
+
127
+#[derive(Clone, Default)]
128
+pub struct Handler {
129
+ pub handle: Option<AppsLookupHandler>,
130
+}
131
+
132
+pub struct ManagedServer {
133
+ inner: raw::ManagedServer,
134
+}
135
+
136
+impl ManagedServer {
137
+ pub fn new(run_dir: &str, service_name: &str, config: ServerConfig, handler: Handler) -> Self {
138
+ Self::with_workers(run_dir, service_name, config, handler, 8)
139
+ }
140
+
141
+ pub fn with_workers(
142
+ run_dir: &str,
143
+ service_name: &str,
144
+ config: ServerConfig,
145
+ handler: Handler,
146
+ worker_count: usize,
147
+ ) -> Self {
148
+ let raw_handler = handler.handle.map(raw::apps_lookup_dispatch);
149
+ Self {
150
+ inner: raw::ManagedServer::with_workers(
151
+ run_dir,
152
+ service_name,
153
+ config.into_transport(),
154
+ METHOD_APPS_LOOKUP,
155
+ raw_handler,
156
+ worker_count,
157
+ ),
158
+ }
159
+ }
160
+
161
+ pub fn run(&mut self) -> Result<(), NipcError> {
162
+ self.inner.run()
163
+ }
164
+
165
+ pub fn stop(&self) {
166
+ self.inner.stop();
167
+ }
168
+
169
+ pub fn running_flag(&self) -> Arc<AtomicBool> {
170
+ self.inner.running_flag()
171
+ }
172
+}
173
+
174
+#[cfg(test)]
175
+mod tests {
176
+ use super::*;
177
+ use crate::protocol::PROFILE_SHM_FUTEX;
178
+ use std::sync::atomic::Ordering;
179
+ use std::sync::Arc;
180
+
181
+ #[test]
182
+ fn client_config_maps_to_transport() {
183
+ let cfg = ClientConfig {
184
+ supported_profiles: PROFILE_BASELINE | PROFILE_SHM_FUTEX,
185
+ preferred_profiles: PROFILE_SHM_FUTEX,
186
+ max_request_batch_items: 17,
187
+ max_response_payload_bytes: 8192,
188
+ auth_token: 99,
189
+ };
190
+
191
+ let transport = cfg.into_transport();
192
+ assert_eq!(
193
+ transport.supported_profiles,
194
+ PROFILE_BASELINE | PROFILE_SHM_FUTEX
195
+ );
196
+ assert_eq!(transport.preferred_profiles, PROFILE_SHM_FUTEX);
197
+ assert_eq!(transport.max_request_batch_items, 17);
198
+ assert_eq!(transport.max_response_batch_items, 17);
199
+ assert_eq!(transport.max_response_payload_bytes, 8192);
200
+ assert_eq!(transport.auth_token, 99);
201
+ }
202
+
203
+ #[test]
204
+ fn server_config_maps_to_transport() {
205
+ let cfg = ServerConfig {
206
+ supported_profiles: PROFILE_BASELINE | PROFILE_SHM_FUTEX,
207
+ preferred_profiles: PROFILE_SHM_FUTEX,
208
+ max_request_batch_items: 23,
209
+ max_response_payload_bytes: 16384,
210
+ auth_token: 123,
211
+ };
212
+
213
+ let transport = cfg.into_transport();
214
+ assert_eq!(
215
+ transport.supported_profiles,
216
+ PROFILE_BASELINE | PROFILE_SHM_FUTEX
217
+ );
218
+ assert_eq!(transport.preferred_profiles, PROFILE_SHM_FUTEX);
219
+ assert_eq!(transport.max_request_batch_items, 23);
220
+ assert_eq!(transport.max_response_batch_items, 23);
221
+ assert_eq!(transport.max_response_payload_bytes, 16384);
222
+ assert_eq!(transport.auth_token, 123);
223
+ }
224
+
225
+ #[test]
226
+ fn managed_server_initializes_stopped_with_handler() {
227
+ let handler = Handler {
228
+ handle: Some(Arc::new(|_, _| true)),
229
+ };
230
+ let server = ManagedServer::with_workers(
231
+ "/tmp/netipc-apps-lookup-test",
232
+ "apps-lookup-facade-test",
233
+ ServerConfig::default(),
234
+ handler,
235
+ 0,
236
+ );
237
+ let running = server.running_flag();
238
+
239
+ assert!(!running.load(Ordering::SeqCst));
240
+ server.stop();
241
+ assert!(!running.load(Ordering::SeqCst));
242
+ }
243
+
244
+ #[cfg(windows)]
245
+ #[test]
246
+ fn client_lifecycle_without_server_windows() {
247
+ let mut client = AppsLookupClient::new(
248
+ r"C:\Temp\nipc-apps-lookup-facade",
249
+ "apps-lookup-facade-no-server",
250
+ ClientConfig::default(),
251
+ );
252
+ assert!(!client.ready());
253
+ assert!(client.refresh());
254
+ assert_eq!(client.status().state, ClientState::NotFound);
255
+
256
+ assert_eq!(client.call(&[123]).unwrap_err(), NipcError::BadLayout);
257
+ assert_eq!(client.status().error_count, 1);
258
+
259
+ client.close();
260
+ assert_eq!(client.status().state, ClientState::Disconnected);
261
+ }
262
+
263
+ #[cfg(windows)]
264
+ #[test]
265
+ fn managed_server_new_initializes_stopped_windows() {
266
+ let server = ManagedServer::new(
267
+ r"C:\Temp\nipc-apps-lookup-facade",
268
+ "apps-lookup-facade-new",
269
+ ServerConfig::default(),
270
+ Handler::default(),
271
+ );
272
+ let running = server.running_flag();
273
+ assert!(!running.load(Ordering::SeqCst));
274
+ server.stop();
275
+ assert!(!running.load(Ordering::SeqCst));
276
+ }
277
+}
src/crates/netipc/src/service/cgroups.rs
+3
-262
@@ -1,264 +1,5 @@
1
-//! L2 cgroups-snapshot service facade.
1
+//! Compatibility module for the historical cgroups-snapshot service path.
2
//!
3
-//! The public service surface is service-kind specific: one endpoint, one
4
-//! request kind. The request code remains in the outer envelope only for
5
-//! validation, not for public multi-method dispatch.
3
+//! New code should prefer [`crate::service::cgroups_snapshot`].
4
7
-use super::raw;
8
-use crate::protocol::{CgroupsResponseView, NipcError, METHOD_CGROUPS_SNAPSHOT, PROFILE_BASELINE};
9
-
10
-#[cfg(unix)]
11
-use crate::transport::posix::{
12
- ClientConfig as TransportClientConfig, ServerConfig as TransportServerConfig,
13
-};
14
-
15
-#[cfg(windows)]
16
-use crate::transport::windows::{
17
- ClientConfig as TransportClientConfig, ServerConfig as TransportServerConfig,
18
-};
19
-
20
-use std::sync::atomic::AtomicBool;
21
-use std::sync::Arc;
22
-
23
-pub use raw::{CgroupsCacheItem, CgroupsCacheStatus, ClientState, ClientStatus, SnapshotHandler};
24
-
25
-/// Public L2/L3 client configuration for the cgroups-snapshot service.
26
-///
27
-/// This service-level configuration is shared across supported operating
28
-/// systems. Transport-only tuning stays below the public typed API.
29
-#[derive(Debug, Clone)]
30
-pub struct ClientConfig {
31
- pub supported_profiles: u32,
32
- pub preferred_profiles: u32,
33
- pub max_request_batch_items: u32,
34
- pub max_response_payload_bytes: u32,
35
- pub auth_token: u64,
36
-}
37
-
38
-impl Default for ClientConfig {
39
- fn default() -> Self {
40
- Self {
41
- supported_profiles: PROFILE_BASELINE,
42
- preferred_profiles: 0,
43
- max_request_batch_items: 0,
44
- max_response_payload_bytes: 0,
45
- auth_token: 0,
46
- }
47
- }
48
-}
49
-
50
-impl ClientConfig {
51
- fn into_transport(self) -> TransportClientConfig {
52
- let mut transport = TransportClientConfig::default();
53
- transport.supported_profiles = self.supported_profiles;
54
- transport.preferred_profiles = self.preferred_profiles;
55
- transport.max_request_batch_items = self.max_request_batch_items;
56
- transport.max_response_payload_bytes = self.max_response_payload_bytes;
57
- transport.max_response_batch_items = self.max_request_batch_items;
58
- transport.auth_token = self.auth_token;
59
- transport
60
- }
61
-}
62
-
63
-/// Public typed-server configuration for the cgroups-snapshot service.
64
-///
65
-/// This configuration is intentionally transport-agnostic. Transport-only
66
-/// knobs such as socket backlog or packet sizing stay below the service layer.
67
-#[derive(Debug, Clone)]
68
-pub struct ServerConfig {
69
- pub supported_profiles: u32,
70
- pub preferred_profiles: u32,
71
- pub max_request_batch_items: u32,
72
- pub max_response_payload_bytes: u32,
73
- pub auth_token: u64,
74
-}
75
-
76
-impl Default for ServerConfig {
77
- fn default() -> Self {
78
- Self {
79
- supported_profiles: PROFILE_BASELINE,
80
- preferred_profiles: 0,
81
- max_request_batch_items: 0,
82
- max_response_payload_bytes: 0,
83
- auth_token: 0,
84
- }
85
- }
86
-}
87
-
88
-impl ServerConfig {
89
- fn into_transport(self) -> TransportServerConfig {
90
- let mut transport = TransportServerConfig::default();
91
- transport.supported_profiles = self.supported_profiles;
92
- transport.preferred_profiles = self.preferred_profiles;
93
- transport.max_request_batch_items = self.max_request_batch_items;
94
- transport.max_response_payload_bytes = self.max_response_payload_bytes;
95
- transport.max_response_batch_items = self.max_request_batch_items;
96
- transport.auth_token = self.auth_token;
97
- transport
98
- }
99
-}
100
-
101
-/// L2 client context for the cgroups-snapshot service.
102
-pub struct CgroupsClient {
103
- inner: raw::RawClient,
104
-}
105
-
106
-impl CgroupsClient {
107
- /// Create a new client context. Does NOT connect. Does NOT require the
108
- /// server to be running.
109
- pub fn new(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
110
- Self {
111
- inner: raw::RawClient::new_snapshot(run_dir, service_name, config.into_transport()),
112
- }
113
- }
114
-
115
- /// Attempt connect if DISCONNECTED/NOT_FOUND, reconnect if BROKEN.
116
- /// Returns true if the state changed.
117
- pub fn refresh(&mut self) -> bool {
118
- self.inner.refresh()
119
- }
120
-
121
- /// Cheap cached boolean. No I/O, no syscalls.
122
- #[inline]
123
- pub fn ready(&self) -> bool {
124
- self.inner.ready()
125
- }
126
-
127
- /// Detailed status snapshot for diagnostics.
128
- pub fn status(&self) -> ClientStatus {
129
- self.inner.status()
130
- }
131
-
132
- /// Blocking typed call for the cgroups-snapshot service.
133
- pub fn call_snapshot(&mut self) -> Result<CgroupsResponseView<'_>, NipcError> {
134
- self.inner.call_snapshot()
135
- }
136
-
137
- /// Tear down connection and release resources.
138
- pub fn close(&mut self) {
139
- self.inner.close();
140
- }
141
-}
142
-
143
-impl Drop for CgroupsClient {
144
- fn drop(&mut self) {
145
- self.close();
146
- }
147
-}
148
-
149
-/// Typed server handler surface for the cgroups-snapshot service.
150
-#[derive(Clone, Default)]
151
-pub struct Handler {
152
- pub handle: Option<SnapshotHandler>,
153
- pub snapshot_max_items: u32,
154
-}
155
-
156
-/// Managed server for the cgroups-snapshot service kind.
157
-pub struct ManagedServer {
158
- inner: raw::ManagedServer,
159
-}
160
-
161
-impl ManagedServer {
162
- /// Create a new managed server. Does NOT start listening yet.
163
- pub fn new(run_dir: &str, service_name: &str, config: ServerConfig, handler: Handler) -> Self {
164
- Self::with_workers(run_dir, service_name, config, handler, 8)
165
- }
166
-
167
- /// Create a server with an explicit worker count limit.
168
- pub fn with_workers(
169
- run_dir: &str,
170
- service_name: &str,
171
- config: ServerConfig,
172
- handler: Handler,
173
- worker_count: usize,
174
- ) -> Self {
175
- let raw_handler = handler
176
- .handle
177
- .map(|handle| raw::snapshot_dispatch(handle, handler.snapshot_max_items));
178
-
179
- Self {
180
- inner: raw::ManagedServer::with_workers(
181
- run_dir,
182
- service_name,
183
- config.into_transport(),
184
- METHOD_CGROUPS_SNAPSHOT,
185
- raw_handler,
186
- worker_count,
187
- ),
188
- }
189
- }
190
-
191
- /// Run the acceptor loop. Blocking. Returns when `stop()` is called or on
192
- /// fatal error.
193
- pub fn run(&mut self) -> Result<(), NipcError> {
194
- self.inner.run()
195
- }
196
-
197
- /// Signal shutdown.
198
- pub fn stop(&self) {
199
- self.inner.stop();
200
- }
201
-
202
- /// Clone of the internal running flag for diagnostics and test helpers.
203
- ///
204
- /// For reliable shutdown, call `stop()`. On Windows, changing this flag
205
- /// alone does not wake a blocking listener accept.
206
- pub fn running_flag(&self) -> Arc<AtomicBool> {
207
- self.inner.running_flag()
208
- }
209
-}
210
-
211
-/// L3 client-side cgroups snapshot cache.
212
-pub struct CgroupsCache {
213
- inner: raw::CgroupsCache,
214
-}
215
-
216
-impl CgroupsCache {
217
- /// Create a new L3 cache. Creates the underlying L2 client context.
218
- /// Does NOT connect. Does NOT require the server to be running.
219
- pub fn new(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
220
- Self {
221
- inner: raw::CgroupsCache::new(run_dir, service_name, config.into_transport()),
222
- }
223
- }
224
-
225
- /// Refresh the cache. Returns true if the cache was updated.
226
- pub fn refresh(&mut self) -> bool {
227
- self.inner.refresh()
228
- }
229
-
230
- /// Returns true if at least one successful refresh has occurred.
231
- #[inline]
232
- pub fn ready(&self) -> bool {
233
- self.inner.ready()
234
- }
235
-
236
- /// Look up a cached item by hash + name. O(1), no I/O.
237
- pub fn lookup(&self, hash: u32, name: &str) -> Option<&CgroupsCacheItem> {
238
- self.inner.lookup(hash, name)
239
- }
240
-
241
- /// Fill a status snapshot for diagnostics.
242
- pub fn status(&self) -> CgroupsCacheStatus {
243
- self.inner.status()
244
- }
245
-
246
- /// Close the cache and underlying L2 client.
247
- pub fn close(&mut self) {
248
- self.inner.close();
249
- }
250
-}
251
-
252
-impl Drop for CgroupsCache {
253
- fn drop(&mut self) {
254
- self.close();
255
- }
256
-}
257
-
258
-#[cfg(all(test, unix))]
259
-#[path = "cgroups_unix_tests.rs"]
260
-mod tests;
261
-
262
-#[cfg(all(test, windows))]
263
-#[path = "cgroups_windows_tests.rs"]
264
-mod windows_tests;
5
+pub use super::cgroups_snapshot::*;
src/crates/netipc/src/service/cgroups_lookup.rs
new
+284
@@ -0,0 +1,284 @@
1
+//! L2 cgroups-lookup service facade.
2
+
3
+use super::raw;
4
+use crate::protocol::{
5
+ CgroupsLookupResponseView, NipcError, METHOD_CGROUPS_LOOKUP, PROFILE_BASELINE,
6
+};
7
+
8
+#[cfg(unix)]
9
+use crate::transport::posix::{
10
+ ClientConfig as TransportClientConfig, ServerConfig as TransportServerConfig,
11
+};
12
+
13
+#[cfg(windows)]
14
+use crate::transport::windows::{
15
+ ClientConfig as TransportClientConfig, ServerConfig as TransportServerConfig,
16
+};
17
+
18
+use std::sync::atomic::AtomicBool;
19
+use std::sync::Arc;
20
+
21
+pub use raw::{CgroupsLookupHandler, ClientState, ClientStatus};
22
+
23
+#[derive(Debug, Clone)]
24
+pub struct ClientConfig {
25
+ pub supported_profiles: u32,
26
+ pub preferred_profiles: u32,
27
+ pub max_request_batch_items: u32,
28
+ pub max_response_payload_bytes: u32,
29
+ pub auth_token: u64,
30
+}
31
+
32
+impl Default for ClientConfig {
33
+ fn default() -> Self {
34
+ Self {
35
+ supported_profiles: PROFILE_BASELINE,
36
+ preferred_profiles: 0,
37
+ max_request_batch_items: 0,
38
+ max_response_payload_bytes: 0,
39
+ auth_token: 0,
40
+ }
41
+ }
42
+}
43
+
44
+impl ClientConfig {
45
+ fn into_transport(self) -> TransportClientConfig {
46
+ let mut transport = TransportClientConfig::default();
47
+ transport.supported_profiles = self.supported_profiles;
48
+ transport.preferred_profiles = self.preferred_profiles;
49
+ transport.max_request_batch_items = self.max_request_batch_items;
50
+ transport.max_response_payload_bytes = self.max_response_payload_bytes;
51
+ transport.max_response_batch_items = self.max_request_batch_items;
52
+ transport.auth_token = self.auth_token;
53
+ transport
54
+ }
55
+}
56
+
57
+#[derive(Debug, Clone)]
58
+pub struct ServerConfig {
59
+ pub supported_profiles: u32,
60
+ pub preferred_profiles: u32,
61
+ pub max_request_batch_items: u32,
62
+ pub max_response_payload_bytes: u32,
63
+ pub auth_token: u64,
64
+}
65
+
66
+impl Default for ServerConfig {
67
+ fn default() -> Self {
68
+ Self {
69
+ supported_profiles: PROFILE_BASELINE,
70
+ preferred_profiles: 0,
71
+ max_request_batch_items: 0,
72
+ max_response_payload_bytes: 0,
73
+ auth_token: 0,
74
+ }
75
+ }
76
+}
77
+
78
+impl ServerConfig {
79
+ fn into_transport(self) -> TransportServerConfig {
80
+ let mut transport = TransportServerConfig::default();
81
+ transport.supported_profiles = self.supported_profiles;
82
+ transport.preferred_profiles = self.preferred_profiles;
83
+ transport.max_request_batch_items = self.max_request_batch_items;
84
+ transport.max_response_payload_bytes = self.max_response_payload_bytes;
85
+ transport.max_response_batch_items = self.max_request_batch_items;
86
+ transport.auth_token = self.auth_token;
87
+ transport
88
+ }
89
+}
90
+
91
+pub struct CgroupsLookupClient {
92
+ inner: raw::RawClient,
93
+}
94
+
95
+impl CgroupsLookupClient {
96
+ pub fn new(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
97
+ Self {
98
+ inner: raw::RawClient::new_cgroups_lookup(
99
+ run_dir,
100
+ service_name,
101
+ config.into_transport(),
102
+ ),
103
+ }
104
+ }
105
+
106
+ pub fn refresh(&mut self) -> bool {
107
+ self.inner.refresh()
108
+ }
109
+
110
+ pub fn ready(&self) -> bool {
111
+ self.inner.ready()
112
+ }
113
+
114
+ pub fn status(&self) -> ClientStatus {
115
+ self.inner.status()
116
+ }
117
+
118
+ pub fn call(&mut self, paths: &[&[u8]]) -> Result<CgroupsLookupResponseView<'_>, NipcError> {
119
+ self.inner.call_cgroups_lookup(paths)
120
+ }
121
+
122
+ pub fn close(&mut self) {
123
+ self.inner.close();
124
+ }
125
+}
126
+
127
+impl Drop for CgroupsLookupClient {
128
+ fn drop(&mut self) {
129
+ self.close();
130
+ }
131
+}
132
+
133
+#[derive(Clone, Default)]
134
+pub struct Handler {
135
+ pub handle: Option<CgroupsLookupHandler>,
136
+}
137
+
138
+pub struct ManagedServer {
139
+ inner: raw::ManagedServer,
140
+}
141
+
142
+impl ManagedServer {
143
+ pub fn new(run_dir: &str, service_name: &str, config: ServerConfig, handler: Handler) -> Self {
144
+ Self::with_workers(run_dir, service_name, config, handler, 8)
145
+ }
146
+
147
+ pub fn with_workers(
148
+ run_dir: &str,
149
+ service_name: &str,
150
+ config: ServerConfig,
151
+ handler: Handler,
152
+ worker_count: usize,
153
+ ) -> Self {
154
+ let raw_handler = handler.handle.map(raw::cgroups_lookup_dispatch);
155
+ Self {
156
+ inner: raw::ManagedServer::with_workers(
157
+ run_dir,
158
+ service_name,
159
+ config.into_transport(),
160
+ METHOD_CGROUPS_LOOKUP,
161
+ raw_handler,
162
+ worker_count,
163
+ ),
164
+ }
165
+ }
166
+
167
+ pub fn run(&mut self) -> Result<(), NipcError> {
168
+ self.inner.run()
169
+ }
170
+
171
+ pub fn stop(&self) {
172
+ self.inner.stop();
173
+ }
174
+
175
+ pub fn running_flag(&self) -> Arc<AtomicBool> {
176
+ self.inner.running_flag()
177
+ }
178
+}
179
+
180
+#[cfg(test)]
181
+mod tests {
182
+ use super::*;
183
+ use crate::protocol::PROFILE_SHM_FUTEX;
184
+ use std::sync::atomic::Ordering;
185
+ use std::sync::Arc;
186
+
187
+ #[test]
188
+ fn client_config_maps_to_transport() {
189
+ let cfg = ClientConfig {
190
+ supported_profiles: PROFILE_BASELINE | PROFILE_SHM_FUTEX,
191
+ preferred_profiles: PROFILE_SHM_FUTEX,
192
+ max_request_batch_items: 17,
193
+ max_response_payload_bytes: 8192,
194
+ auth_token: 99,
195
+ };
196
+
197
+ let transport = cfg.into_transport();
198
+ assert_eq!(
199
+ transport.supported_profiles,
200
+ PROFILE_BASELINE | PROFILE_SHM_FUTEX
201
+ );
202
+ assert_eq!(transport.preferred_profiles, PROFILE_SHM_FUTEX);
203
+ assert_eq!(transport.max_request_batch_items, 17);
204
+ assert_eq!(transport.max_response_batch_items, 17);
205
+ assert_eq!(transport.max_response_payload_bytes, 8192);
206
+ assert_eq!(transport.auth_token, 99);
207
+ }
208
+
209
+ #[test]
210
+ fn server_config_maps_to_transport() {
211
+ let cfg = ServerConfig {
212
+ supported_profiles: PROFILE_BASELINE | PROFILE_SHM_FUTEX,
213
+ preferred_profiles: PROFILE_SHM_FUTEX,
214
+ max_request_batch_items: 23,
215
+ max_response_payload_bytes: 16384,
216
+ auth_token: 123,
217
+ };
218
+
219
+ let transport = cfg.into_transport();
220
+ assert_eq!(
221
+ transport.supported_profiles,
222
+ PROFILE_BASELINE | PROFILE_SHM_FUTEX
223
+ );
224
+ assert_eq!(transport.preferred_profiles, PROFILE_SHM_FUTEX);
225
+ assert_eq!(transport.max_request_batch_items, 23);
226
+ assert_eq!(transport.max_response_batch_items, 23);
227
+ assert_eq!(transport.max_response_payload_bytes, 16384);
228
+ assert_eq!(transport.auth_token, 123);
229
+ }
230
+
231
+ #[test]
232
+ fn managed_server_initializes_stopped_with_handler() {
233
+ let handler = Handler {
234
+ handle: Some(Arc::new(|_, _| true)),
235
+ };
236
+ let server = ManagedServer::with_workers(
237
+ "/tmp/netipc-cgroups-lookup-test",
238
+ "cgroups-lookup-facade-test",
239
+ ServerConfig::default(),
240
+ handler,
241
+ 0,
242
+ );
243
+ let running = server.running_flag();
244
+
245
+ assert!(!running.load(Ordering::SeqCst));
246
+ server.stop();
247
+ assert!(!running.load(Ordering::SeqCst));
248
+ }
249
+
250
+ #[cfg(windows)]
251
+ #[test]
252
+ fn client_lifecycle_without_server_windows() {
253
+ let mut client = CgroupsLookupClient::new(
254
+ r"C:\Temp\nipc-cgroups-lookup-facade",
255
+ "cgroups-lookup-facade-no-server",
256
+ ClientConfig::default(),
257
+ );
258
+ assert!(!client.ready());
259
+ assert!(client.refresh());
260
+ assert_eq!(client.status().state, ClientState::NotFound);
261
+
262
+ let paths: [&[u8]; 1] = [b"/x".as_slice()];
263
+ assert_eq!(client.call(&paths).unwrap_err(), NipcError::BadLayout);
264
+ assert_eq!(client.status().error_count, 1);
265
+
266
+ client.close();
267
+ assert_eq!(client.status().state, ClientState::Disconnected);
268
+ }
269
+
270
+ #[cfg(windows)]
271
+ #[test]
272
+ fn managed_server_new_initializes_stopped_windows() {
273
+ let server = ManagedServer::new(
274
+ r"C:\Temp\nipc-cgroups-lookup-facade",
275
+ "cgroups-lookup-facade-new",
276
+ ServerConfig::default(),
277
+ Handler::default(),
278
+ );
279
+ let running = server.running_flag();
280
+ assert!(!running.load(Ordering::SeqCst));
281
+ server.stop();
282
+ assert!(!running.load(Ordering::SeqCst));
283
+ }
284
+}
src/crates/netipc/src/service/cgroups_snapshot.rs
new
+264
@@ -0,0 +1,264 @@
1
+//! L2 cgroups-snapshot service facade.
2
+//!
3
+//! The public service surface is service-kind specific: one endpoint, one
4
+//! request kind. The request code remains in the outer envelope only for
5
+//! validation, not for public multi-method dispatch.
6
+
7
+use super::raw;
8
+use crate::protocol::{CgroupsResponseView, NipcError, METHOD_CGROUPS_SNAPSHOT, PROFILE_BASELINE};
9
+
10
+#[cfg(unix)]
11
+use crate::transport::posix::{
12
+ ClientConfig as TransportClientConfig, ServerConfig as TransportServerConfig,
13
+};
14
+
15
+#[cfg(windows)]
16
+use crate::transport::windows::{
17
+ ClientConfig as TransportClientConfig, ServerConfig as TransportServerConfig,
18
+};
19
+
20
+use std::sync::atomic::AtomicBool;
21
+use std::sync::Arc;
22
+
23
+pub use raw::{CgroupsCacheItem, CgroupsCacheStatus, ClientState, ClientStatus, SnapshotHandler};
24
+
25
+/// Public L2/L3 client configuration for the cgroups-snapshot service.
26
+///
27
+/// This service-level configuration is shared across supported operating
28
+/// systems. Transport-only tuning stays below the public typed API.
29
+#[derive(Debug, Clone)]
30
+pub struct ClientConfig {
31
+ pub supported_profiles: u32,
32
+ pub preferred_profiles: u32,
33
+ pub max_request_batch_items: u32,
34
+ pub max_response_payload_bytes: u32,
35
+ pub auth_token: u64,
36
+}
37
+
38
+impl Default for ClientConfig {
39
+ fn default() -> Self {
40
+ Self {
41
+ supported_profiles: PROFILE_BASELINE,
42
+ preferred_profiles: 0,
43
+ max_request_batch_items: 0,
44
+ max_response_payload_bytes: 0,
45
+ auth_token: 0,
46
+ }
47
+ }
48
+}
49
+
50
+impl ClientConfig {
51
+ fn into_transport(self) -> TransportClientConfig {
52
+ let mut transport = TransportClientConfig::default();
53
+ transport.supported_profiles = self.supported_profiles;
54
+ transport.preferred_profiles = self.preferred_profiles;
55
+ transport.max_request_batch_items = self.max_request_batch_items;
56
+ transport.max_response_payload_bytes = self.max_response_payload_bytes;
57
+ transport.max_response_batch_items = self.max_request_batch_items;
58
+ transport.auth_token = self.auth_token;
59
+ transport
60
+ }
61
+}
62
+
63
+/// Public typed-server configuration for the cgroups-snapshot service.
64
+///
65
+/// This configuration is intentionally transport-agnostic. Transport-only
66
+/// knobs such as socket backlog or packet sizing stay below the service layer.
67
+#[derive(Debug, Clone)]
68
+pub struct ServerConfig {
69
+ pub supported_profiles: u32,
70
+ pub preferred_profiles: u32,
71
+ pub max_request_batch_items: u32,
72
+ pub max_response_payload_bytes: u32,
73
+ pub auth_token: u64,
74
+}
75
+
76
+impl Default for ServerConfig {
77
+ fn default() -> Self {
78
+ Self {
79
+ supported_profiles: PROFILE_BASELINE,
80
+ preferred_profiles: 0,
81
+ max_request_batch_items: 0,
82
+ max_response_payload_bytes: 0,
83
+ auth_token: 0,
84
+ }
85
+ }
86
+}
87
+
88
+impl ServerConfig {
89
+ fn into_transport(self) -> TransportServerConfig {
90
+ let mut transport = TransportServerConfig::default();
91
+ transport.supported_profiles = self.supported_profiles;
92
+ transport.preferred_profiles = self.preferred_profiles;
93
+ transport.max_request_batch_items = self.max_request_batch_items;
94
+ transport.max_response_payload_bytes = self.max_response_payload_bytes;
95
+ transport.max_response_batch_items = self.max_request_batch_items;
96
+ transport.auth_token = self.auth_token;
97
+ transport
98
+ }
99
+}
100
+
101
+/// L2 client context for the cgroups-snapshot service.
102
+pub struct CgroupsClient {
103
+ inner: raw::RawClient,
104
+}
105
+
106
+impl CgroupsClient {
107
+ /// Create a new client context. Does NOT connect. Does NOT require the
108
+ /// server to be running.
109
+ pub fn new(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
110
+ Self {
111
+ inner: raw::RawClient::new_snapshot(run_dir, service_name, config.into_transport()),
112
+ }
113
+ }
114
+
115
+ /// Attempt connect if DISCONNECTED/NOT_FOUND, reconnect if BROKEN.
116
+ /// Returns true if the state changed.
117
+ pub fn refresh(&mut self) -> bool {
118
+ self.inner.refresh()
119
+ }
120
+
121
+ /// Cheap cached boolean. No I/O, no syscalls.
122
+ #[inline]
123
+ pub fn ready(&self) -> bool {
124
+ self.inner.ready()
125
+ }
126
+
127
+ /// Detailed status snapshot for diagnostics.
128
+ pub fn status(&self) -> ClientStatus {
129
+ self.inner.status()
130
+ }
131
+
132
+ /// Blocking typed call for the cgroups-snapshot service.
133
+ pub fn call_snapshot(&mut self) -> Result<CgroupsResponseView<'_>, NipcError> {
134
+ self.inner.call_snapshot()
135
+ }
136
+
137
+ /// Tear down connection and release resources.
138
+ pub fn close(&mut self) {
139
+ self.inner.close();
140
+ }
141
+}
142
+
143
+impl Drop for CgroupsClient {
144
+ fn drop(&mut self) {
145
+ self.close();
146
+ }
147
+}
148
+
149
+/// Typed server handler surface for the cgroups-snapshot service.
150
+#[derive(Clone, Default)]
151
+pub struct Handler {
152
+ pub handle: Option<SnapshotHandler>,
153
+ pub snapshot_max_items: u32,
154
+}
155
+
156
+/// Managed server for the cgroups-snapshot service kind.
157
+pub struct ManagedServer {
158
+ inner: raw::ManagedServer,
159
+}
160
+
161
+impl ManagedServer {
162
+ /// Create a new managed server. Does NOT start listening yet.
163
+ pub fn new(run_dir: &str, service_name: &str, config: ServerConfig, handler: Handler) -> Self {
164
+ Self::with_workers(run_dir, service_name, config, handler, 8)
165
+ }
166
+
167
+ /// Create a server with an explicit worker count limit.
168
+ pub fn with_workers(
169
+ run_dir: &str,
170
+ service_name: &str,
171
+ config: ServerConfig,
172
+ handler: Handler,
173
+ worker_count: usize,
174
+ ) -> Self {
175
+ let raw_handler = handler
176
+ .handle
177
+ .map(|handle| raw::snapshot_dispatch(handle, handler.snapshot_max_items));
178
+
179
+ Self {
180
+ inner: raw::ManagedServer::with_workers(
181
+ run_dir,
182
+ service_name,
183
+ config.into_transport(),
184
+ METHOD_CGROUPS_SNAPSHOT,
185
+ raw_handler,
186
+ worker_count,
187
+ ),
188
+ }
189
+ }
190
+
191
+ /// Run the acceptor loop. Blocking. Returns when `stop()` is called or on
192
+ /// fatal error.
193
+ pub fn run(&mut self) -> Result<(), NipcError> {
194
+ self.inner.run()
195
+ }
196
+
197
+ /// Signal shutdown.
198
+ pub fn stop(&self) {
199
+ self.inner.stop();
200
+ }
201
+
202
+ /// Clone of the internal running flag for diagnostics and test helpers.
203
+ ///
204
+ /// For reliable shutdown, call `stop()`. On Windows, changing this flag
205
+ /// alone does not wake a blocking listener accept.
206
+ pub fn running_flag(&self) -> Arc<AtomicBool> {
207
+ self.inner.running_flag()
208
+ }
209
+}
210
+
211
+/// L3 client-side cgroups snapshot cache.
212
+pub struct CgroupsCache {
213
+ inner: raw::CgroupsCache,
214
+}
215
+
216
+impl CgroupsCache {
217
+ /// Create a new L3 cache. Creates the underlying L2 client context.
218
+ /// Does NOT connect. Does NOT require the server to be running.
219
+ pub fn new(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
220
+ Self {
221
+ inner: raw::CgroupsCache::new(run_dir, service_name, config.into_transport()),
222
+ }
223
+ }
224
+
225
+ /// Refresh the cache. Returns true if the cache was updated.
226
+ pub fn refresh(&mut self) -> bool {
227
+ self.inner.refresh()
228
+ }
229
+
230
+ /// Returns true if at least one successful refresh has occurred.
231
+ #[inline]
232
+ pub fn ready(&self) -> bool {
233
+ self.inner.ready()
234
+ }
235
+
236
+ /// Look up a cached item by hash + name. O(1), no I/O.
237
+ pub fn lookup(&self, hash: u32, name: &str) -> Option<&CgroupsCacheItem> {
238
+ self.inner.lookup(hash, name)
239
+ }
240
+
241
+ /// Fill a status snapshot for diagnostics.
242
+ pub fn status(&self) -> CgroupsCacheStatus {
243
+ self.inner.status()
244
+ }
245
+
246
+ /// Close the cache and underlying L2 client.
247
+ pub fn close(&mut self) {
248
+ self.inner.close();
249
+ }
250
+}
251
+
252
+impl Drop for CgroupsCache {
253
+ fn drop(&mut self) {
254
+ self.close();
255
+ }
256
+}
257
+
258
+#[cfg(all(test, unix))]
259
+#[path = "cgroups_unix_tests.rs"]
260
+mod tests;
261
+
262
+#[cfg(all(test, windows))]
263
+#[path = "cgroups_windows_tests.rs"]
264
+mod windows_tests;
src/crates/netipc/src/service/mod.rs
+3
@@ -4,6 +4,9 @@
4
//! remain generic for tests and benchmarks, but every running endpoint still
5
//! serves exactly one request kind.
6
7
+pub mod apps_lookup;
8
pub mod cgroups;
9
+pub mod cgroups_lookup;
10
+pub mod cgroups_snapshot;
11
#[doc(hidden)]
12
pub mod raw;
src/crates/netipc/src/service/raw.rs
+53
-2622
@@ -4,2635 +4,66 @@
4
//! internal benchmark/stress coverage while the public service modules expose
5
//! one service kind per endpoint.
6
7
-use crate::protocol::{
8
- self, batch_item_get, increment_decode, increment_encode, string_reverse_decode,
9
- string_reverse_encode, BatchBuilder, CgroupsRequest, CgroupsResponseView, Header, NipcError,
10
- FLAG_BATCH, HEADER_SIZE, INCREMENT_PAYLOAD_SIZE, KIND_REQUEST, KIND_RESPONSE, MAGIC_MSG,
11
- MAX_PAYLOAD_CAP, MAX_PAYLOAD_DEFAULT, METHOD_CGROUPS_SNAPSHOT, METHOD_INCREMENT,
12
- METHOD_STRING_REVERSE, STATUS_BAD_ENVELOPE, STATUS_INTERNAL_ERROR, STATUS_LIMIT_EXCEEDED,
13
- STATUS_OK, STRING_REVERSE_HDR_SIZE, VERSION,
14
-};
15
-
7
+mod apps_lookup;
8
+mod cgroups_cache;
9
+mod cgroups_lookup;
10
+mod cgroups_snapshot;
11
+mod client;
12
+mod client_call;
13
#[cfg(unix)]
17
-use crate::protocol::{PROFILE_SHM_FUTEX, PROFILE_SHM_HYBRID};
18
-
14
+mod client_unix;
15
+#[cfg(windows)]
16
+mod client_windows;
17
+mod common;
18
+mod dispatch;
19
+mod increment;
20
+mod server;
21
#[cfg(unix)]
20
-use crate::transport::posix::{ClientConfig, ServerConfig, UdsListener, UdsSession};
21
-
22
-#[cfg(target_os = "linux")]
23
-use crate::transport::shm::ShmContext;
24
-
22
+mod server_session_unix;
23
#[cfg(windows)]
26
-use crate::transport::windows::{ClientConfig, NpError, NpListener, NpSession, ServerConfig};
27
-
24
+mod server_session_windows;
25
+#[cfg(unix)]
26
+mod server_unix;
27
#[cfg(windows)]
29
-use crate::transport::win_shm::{
30
- WinShmContext, PROFILE_BUSYWAIT as WIN_SHM_PROFILE_BUSYWAIT,
31
- PROFILE_HYBRID as WIN_SHM_PROFILE_HYBRID,
32
-};
28
+mod server_windows;
29
+mod string_reverse;
30
+
31
+pub use apps_lookup::{apps_lookup_dispatch, AppsLookupHandler};
32
+pub use cgroups_cache::{CgroupsCache, CgroupsCacheItem, CgroupsCacheStatus};
33
+pub use cgroups_lookup::{cgroups_lookup_dispatch, CgroupsLookupHandler};
34
+pub use cgroups_snapshot::{snapshot_dispatch, snapshot_max_items, SnapshotHandler};
35
+pub use client::{ClientState, ClientStatus, RawClient};
36
+pub use dispatch::{DispatchError, DispatchHandler};
37
+pub use increment::{increment_dispatch, IncrementHandler};
38
+pub use server::ManagedServer;
39
+pub use string_reverse::{string_reverse_dispatch, StringReverseHandler};
40
34
-use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
41
+#[cfg(all(test, unix))]
42
+use crate::protocol::{
43
+ self, batch_item_get, increment_decode, string_reverse_decode, string_reverse_encode,
44
+ CgroupsRequest, Header, NipcError, FLAG_BATCH, HEADER_SIZE, INCREMENT_PAYLOAD_SIZE,
45
+ KIND_REQUEST, KIND_RESPONSE, MAGIC_MSG, METHOD_CGROUPS_SNAPSHOT, METHOD_INCREMENT,
46
+ METHOD_STRING_REVERSE, STATUS_BAD_ENVELOPE, STATUS_INTERNAL_ERROR, STATUS_OK, VERSION,
47
+};
48
+#[cfg(all(test, unix))]
49
+use crate::transport::posix::{ClientConfig, ServerConfig, UdsListener, UdsSession};
50
+#[cfg(all(test, target_os = "linux"))]
51
+use crate::transport::shm::ShmContext;
52
+#[cfg(all(test, windows))]
53
+use crate::transport::windows::{ClientConfig, NpListener, NpSession, ServerConfig};
54
+#[cfg(all(test, unix))]
55
+use std::sync::atomic::{AtomicBool, Ordering};
56
+#[cfg(all(test, unix))]
57
use std::sync::Arc;
58
37
-/// Poll/receive timeout for server loops (ms). Controls shutdown detection latency.
38
-const SERVER_POLL_TIMEOUT_MS: u32 = 100;
39
-const CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS: u64 = 5;
40
-const CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS: u64 = 5_000;
41
-
42
-fn next_power_of_2_u32(n: u32) -> u32 {
43
- if n < 16 {
44
- return 16;
45
- }
46
-
47
- // Cap at 2^31 — the largest power of 2 that fits in u32
48
- if n > (1u32 << 31) {
49
- return 1u32 << 31;
50
- }
51
-
52
- let mut value = n - 1;
53
- value |= value >> 1;
54
- value |= value >> 2;
55
- value |= value >> 4;
56
- value |= value >> 8;
57
- value |= value >> 16;
58
- value + 1
59
-}
60
-
61
-// ---------------------------------------------------------------------------
62
-// Client state
63
-// ---------------------------------------------------------------------------
64
-
65
-/// Client connection state machine.
66
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67
-pub enum ClientState {
68
- Disconnected,
69
- Connecting,
70
- Ready,
71
- NotFound,
72
- AuthFailed,
73
- Incompatible,
74
- Broken,
75
-}
76
-
77
-/// Diagnostic counters snapshot.
78
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79
-pub struct ClientStatus {
80
- pub state: ClientState,
81
- pub connect_count: u32,
82
- pub reconnect_count: u32,
83
- pub call_count: u32,
84
- pub error_count: u32,
85
-}
86
-
87
-// ---------------------------------------------------------------------------
88
-// Client context
89
-// ---------------------------------------------------------------------------
90
-
91
-/// L2 client context bound to one service kind.
92
-///
93
-/// Manages connection lifecycle and provides typed blocking calls with
94
-/// at-least-once retry semantics. The outer request code remains only for
95
-/// validation; each client instance is bound to one expected request kind.
96
-pub struct RawClient {
97
- state: ClientState,
98
- run_dir: String,
99
- service_name: String,
100
- expected_method_code: u16,
101
- transport_config: ClientConfig,
102
-
103
- // Connection (managed internally)
104
- #[cfg(unix)]
105
- session: Option<UdsSession>,
106
- #[cfg(target_os = "linux")]
107
- shm: Option<ShmContext>,
108
-
109
- #[cfg(windows)]
110
- session: Option<NpSession>,
111
- #[cfg(windows)]
112
- shm: Option<WinShmContext>,
113
-
114
- // Reusable scratch buffers owned by the client for hot request paths.
115
- request_buf: Vec<u8>,
116
- send_buf: Vec<u8>,
117
- transport_buf: Vec<u8>,
118
-
119
- // Stats
120
- connect_count: u32,
121
- reconnect_count: u32,
122
- call_count: u32,
123
- error_count: u32,
124
-}
125
-
126
-impl RawClient {
127
- fn new_bound(
128
- run_dir: &str,
129
- service_name: &str,
130
- expected_method_code: u16,
131
- config: ClientConfig,
132
- ) -> Self {
133
- RawClient {
134
- state: ClientState::Disconnected,
135
- run_dir: run_dir.to_string(),
136
- service_name: service_name.to_string(),
137
- expected_method_code,
138
- transport_config: config,
139
- session: None,
140
- #[cfg(target_os = "linux")]
141
- shm: None,
142
- #[cfg(windows)]
143
- shm: None,
144
- request_buf: Vec::new(),
145
- send_buf: Vec::new(),
146
- transport_buf: Vec::new(),
147
- connect_count: 0,
148
- reconnect_count: 0,
149
- call_count: 0,
150
- error_count: 0,
151
- }
152
- }
153
-
154
- /// Create a new client context bound to the cgroups-snapshot service kind.
155
- /// Does NOT connect. Does NOT require the server to be running.
156
- pub fn new_snapshot(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
157
- Self::new_bound(run_dir, service_name, METHOD_CGROUPS_SNAPSHOT, config)
158
- }
159
-
160
- /// Create a new client context bound to the increment service kind.
161
- /// Does NOT connect. Does NOT require the server to be running.
162
- pub fn new_increment(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
163
- Self::new_bound(run_dir, service_name, METHOD_INCREMENT, config)
164
- }
165
-
166
- /// Create a new client context bound to the string-reverse service kind.
167
- /// Does NOT connect. Does NOT require the server to be running.
168
- pub fn new_string_reverse(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
169
- Self::new_bound(run_dir, service_name, METHOD_STRING_REVERSE, config)
170
- }
171
-
172
- /// Attempt connect if DISCONNECTED/NOT_FOUND, reconnect if BROKEN.
173
- /// Returns true if the state changed.
174
- pub fn refresh(&mut self) -> bool {
175
- let old_state = self.state;
176
-
177
- match self.state {
178
- ClientState::Disconnected | ClientState::NotFound => {
179
- self.state = ClientState::Connecting;
180
- self.state = self.try_connect();
181
- if self.state == ClientState::Ready {
182
- self.connect_count += 1;
183
- }
184
- }
185
- ClientState::Broken => {
186
- self.disconnect();
187
- self.state = ClientState::Connecting;
188
- self.state = self.try_connect();
189
- if self.state == ClientState::Ready {
190
- self.reconnect_count += 1;
191
- }
192
- }
193
- ClientState::Ready
194
- | ClientState::Connecting
195
- | ClientState::AuthFailed
196
- | ClientState::Incompatible => {}
197
- }
198
-
199
- self.state != old_state
200
- }
201
-
202
- /// Cheap cached boolean. No I/O, no syscalls.
203
- #[inline]
204
- pub fn ready(&self) -> bool {
205
- self.state == ClientState::Ready
206
- }
207
-
208
- /// Detailed status snapshot for diagnostics.
209
- pub fn status(&self) -> ClientStatus {
210
- ClientStatus {
211
- state: self.state,
212
- connect_count: self.connect_count,
213
- reconnect_count: self.reconnect_count,
214
- call_count: self.call_count,
215
- error_count: self.error_count,
216
- }
217
- }
218
-
219
- fn session_max_request_payload_bytes(&self) -> u32 {
220
- #[cfg(unix)]
221
- if let Some(ref session) = self.session {
222
- return session.max_request_payload_bytes;
223
- }
224
-
225
- #[cfg(windows)]
226
- if let Some(ref session) = self.session {
227
- return session.max_request_payload_bytes;
228
- }
229
-
230
- self.transport_config.max_request_payload_bytes
231
- }
232
-
233
- fn session_max_response_payload_bytes(&self) -> u32 {
234
- #[cfg(unix)]
235
- if let Some(ref session) = self.session {
236
- return session.max_response_payload_bytes;
237
- }
238
-
239
- #[cfg(windows)]
240
- if let Some(ref session) = self.session {
241
- return session.max_response_payload_bytes;
242
- }
243
-
244
- self.transport_config.max_response_payload_bytes
245
- }
246
-
247
- fn client_note_request_capacity(&mut self, payload_len: u32) {
248
- let grown = next_power_of_2_u32(payload_len).min(MAX_PAYLOAD_CAP);
249
- if grown > self.transport_config.max_request_payload_bytes {
250
- self.transport_config.max_request_payload_bytes = grown;
251
- }
252
- }
253
-
254
- fn client_note_response_capacity(&mut self, payload_len: u32) {
255
- let grown = next_power_of_2_u32(payload_len).min(MAX_PAYLOAD_CAP);
256
- if grown > self.transport_config.max_response_payload_bytes {
257
- self.transport_config.max_response_payload_bytes = grown;
258
- }
259
- }
260
-
261
- fn validate_method(&self, method_code: u16) -> Result<(), NipcError> {
262
- if self.expected_method_code == method_code {
263
- Ok(())
264
- } else {
265
- Err(NipcError::BadLayout)
266
- }
267
- }
268
-
269
- /// Blocking typed call: encode request, send, receive, check
270
- /// transport_status, decode response.
271
- ///
272
- /// The returned view is valid until the next typed call on this client.
273
- ///
274
- /// Retry policy (per spec): if the call fails and the context was
275
- /// previously READY, disconnect, reconnect (full handshake), and retry.
276
- /// Ordinary failures retry once. Overflow-driven resize recovery may
277
- /// reconnect more than once while negotiated capacities grow.
278
- pub fn call_snapshot(&mut self) -> Result<CgroupsResponseView<'_>, NipcError> {
279
- self.validate_method(METHOD_CGROUPS_SNAPSHOT)?;
280
- let req = CgroupsRequest {
281
- layout_version: 1,
282
- flags: 0,
283
- };
284
- let mut req_buf = [0u8; 4];
285
- let req_len = req.encode(&mut req_buf);
286
- if req_len == 0 {
287
- return Err(NipcError::Truncated);
288
- }
289
-
290
- let response = self.raw_call_with_retry(METHOD_CGROUPS_SNAPSHOT, &req_buf[..req_len])?;
291
- CgroupsResponseView::decode(self.response_payload(response)?)
292
- }
293
-
294
- /// Blocking typed call: INCREMENT method.
295
- /// Sends a u64 value, receives the incremented u64 back.
296
- pub fn call_increment(&mut self, value: u64) -> Result<u64, NipcError> {
297
- self.validate_method(METHOD_INCREMENT)?;
298
- let mut req_buf = [0u8; INCREMENT_PAYLOAD_SIZE];
299
- let req_len = increment_encode(value, &mut req_buf);
300
- if req_len == 0 {
301
- return Err(NipcError::Truncated);
302
- }
303
-
304
- let response = self.raw_call_with_retry(METHOD_INCREMENT, &req_buf[..req_len])?;
305
- increment_decode(self.response_payload(response)?)
306
- }
307
-
308
- /// Blocking typed call: STRING_REVERSE method.
309
- /// Sends a string, receives the reversed string back.
310
- ///
311
- /// The returned view is valid until the next typed call on this client.
312
- pub fn call_string_reverse(
313
- &mut self,
314
- s: &str,
315
- ) -> Result<protocol::StringReverseView<'_>, NipcError> {
316
- self.validate_method(METHOD_STRING_REVERSE)?;
317
- let req_size = STRING_REVERSE_HDR_SIZE + s.len() + 1;
318
- let req_buf = ensure_client_scratch(&mut self.request_buf, req_size);
319
- let req_len = string_reverse_encode(s.as_bytes(), req_buf);
320
- if req_len == 0 {
321
- return Err(NipcError::Truncated);
322
- }
323
-
324
- let response = self.raw_call_with_retry_request_buf(METHOD_STRING_REVERSE, req_len)?;
325
- string_reverse_decode(self.response_payload(response)?)
326
- }
327
-
328
- /// Blocking typed batch call: INCREMENT method.
329
- /// Sends multiple u64 values, receives the incremented u64s back.
330
- pub fn call_increment_batch(&mut self, values: &[u64]) -> Result<Vec<u64>, NipcError> {
331
- self.validate_method(METHOD_INCREMENT)?;
332
- if values.is_empty() {
333
- return Ok(Vec::new());
334
- }
335
-
336
- // Single value: use the non-batch path
337
- if values.len() == 1 {
338
- let r = self.call_increment(values[0])?;
339
- return Ok(vec![r]);
340
- }
341
-
342
- let count = values.len() as u32;
343
-
344
- let req_buf_size = protocol::align8(count as usize * 8)
345
- + count as usize * protocol::align8(INCREMENT_PAYLOAD_SIZE)
346
- + 64;
347
- let req_buf = ensure_client_scratch(&mut self.request_buf, req_buf_size);
348
- let req_len = {
349
- let mut bb = BatchBuilder::new(req_buf, count);
350
- for &v in values {
351
- let mut item_buf = [0u8; INCREMENT_PAYLOAD_SIZE];
352
- if increment_encode(v, &mut item_buf) == 0 {
353
- return Err(NipcError::Truncated);
354
- }
355
- bb.add(&item_buf).map_err(|_| NipcError::Overflow)?;
356
- }
357
- let (req_len, _out_count) = bb.finish();
358
- req_len
359
- };
360
-
361
- let response =
362
- self.raw_batch_call_with_retry_request_buf(METHOD_INCREMENT, req_len, count)?;
363
- let resp_payload = self.response_payload(response)?;
364
- let mut results = Vec::with_capacity(values.len());
365
- for i in 0..count {
366
- let (item_data, _item_len) = batch_item_get(resp_payload, count, i)?;
367
- let val = increment_decode(item_data)?;
368
- results.push(val);
369
- }
370
-
371
- Ok(results)
372
- }
373
-
374
- /// Tear down connection and release resources.
375
- pub fn close(&mut self) {
376
- self.disconnect();
377
- self.state = ClientState::Disconnected;
378
- }
379
-
380
- // ------------------------------------------------------------------
381
- // Internal helpers
382
- // ------------------------------------------------------------------
383
-
384
- /// Tear down the current connection.
385
- fn disconnect(&mut self) {
386
- #[cfg(target_os = "linux")]
387
- {
388
- if let Some(mut shm) = self.shm.take() {
389
- shm.close();
390
- }
391
- }
392
-
393
- #[cfg(windows)]
394
- {
395
- if let Some(mut shm) = self.shm.take() {
396
- shm.close();
397
- }
398
- }
399
-
400
- // Drop the session (closes handle/fd via Drop impl)
401
- self.session.take();
402
- }
403
-
404
- /// Attempt a full connection: transport connect + handshake, then SHM
405
- /// upgrade if negotiated.
406
- #[cfg(unix)]
407
- fn try_connect(&mut self) -> ClientState {
408
- match UdsSession::connect(&self.run_dir, &self.service_name, &self.transport_config) {
409
- Ok(session) => {
410
- #[cfg(target_os = "linux")]
411
- let selected_profile = session.selected_profile;
412
- #[cfg(target_os = "linux")]
413
- let session_id = session.session_id;
414
-
415
- // SHM upgrade if negotiated
416
- #[cfg(target_os = "linux")]
417
- {
418
- if selected_profile == PROFILE_SHM_HYBRID
419
- || selected_profile == PROFILE_SHM_FUTEX
420
- {
421
- // Retry attach: server creates the SHM region after
422
- // the UDS handshake, so it may not exist yet.
423
- let mut shm_ok = false;
424
- let deadline = std::time::Instant::now()
425
- + std::time::Duration::from_millis(CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS);
426
- loop {
427
- match ShmContext::client_attach(
428
- &self.run_dir,
429
- &self.service_name,
430
- session_id,
431
- ) {
432
- Ok(ctx) => {
433
- self.shm = Some(ctx);
434
- shm_ok = true;
435
- break;
436
- }
437
- Err(_) => {
438
- if std::time::Instant::now() >= deadline {
439
- break;
440
- }
441
- std::thread::sleep(std::time::Duration::from_millis(
442
- CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS,
443
- ));
444
- }
445
- }
446
- }
447
- if !shm_ok {
448
- // SHM attach failed after negotiation. Close that session,
449
- // blacklist SHM for this client context, and retry baseline.
450
- drop(session);
451
- self.transport_config.supported_profiles &=
452
- !(PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
453
- self.transport_config.preferred_profiles &=
454
- !(PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
455
- if self.transport_config.supported_profiles == 0 {
456
- return ClientState::Disconnected;
457
- }
458
- return self.try_connect();
459
- }
460
- }
461
- }
462
-
463
- self.session = Some(session);
464
- ClientState::Ready
465
- }
466
- Err(e) => {
467
- use crate::transport::posix::UdsError;
468
- match e {
469
- UdsError::Connect(_) => ClientState::NotFound,
470
- UdsError::AuthFailed => ClientState::AuthFailed,
471
- UdsError::NoProfile => ClientState::Incompatible,
472
- UdsError::Incompatible(_) => ClientState::Incompatible,
473
- _ => ClientState::Disconnected,
474
- }
475
- }
476
- }
477
- }
478
-
479
- /// Windows: attempt a full Named Pipe connection + Win SHM upgrade.
480
- #[cfg(windows)]
481
- fn try_connect(&mut self) -> ClientState {
482
- match NpSession::connect(&self.run_dir, &self.service_name, &self.transport_config) {
483
- Ok(session) => {
484
- let selected_profile = session.selected_profile;
485
-
486
- // Win SHM upgrade if negotiated
487
- if selected_profile == WIN_SHM_PROFILE_HYBRID
488
- || selected_profile == WIN_SHM_PROFILE_BUSYWAIT
489
- {
490
- let mut shm_ok = false;
491
- let deadline = std::time::Instant::now()
492
- + std::time::Duration::from_millis(CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS);
493
- loop {
494
- match WinShmContext::client_attach(
495
- &self.run_dir,
496
- &self.service_name,
497
- self.transport_config.auth_token,
498
- session.session_id,
499
- selected_profile,
500
- ) {
501
- Ok(ctx) => {
502
- self.shm = Some(ctx);
503
- shm_ok = true;
504
- break;
505
- }
506
- Err(_) => {
507
- if std::time::Instant::now() >= deadline {
508
- break;
509
- }
510
- std::thread::sleep(std::time::Duration::from_millis(
511
- CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS,
512
- ));
513
- }
514
- }
515
- }
516
- if !shm_ok {
517
- // WinSHM attach failed after negotiation. Close that
518
- // session, blacklist WinSHM for this client context,
519
- // and retry baseline.
520
- drop(session);
521
- self.transport_config.supported_profiles &=
522
- !(WIN_SHM_PROFILE_HYBRID | WIN_SHM_PROFILE_BUSYWAIT);
523
- self.transport_config.preferred_profiles &=
524
- !(WIN_SHM_PROFILE_HYBRID | WIN_SHM_PROFILE_BUSYWAIT);
525
- if self.transport_config.supported_profiles == 0 {
526
- return ClientState::Disconnected;
527
- }
528
- return self.try_connect();
529
- }
530
- }
531
-
532
- self.session = Some(session);
533
- ClientState::Ready
534
- }
535
- Err(e) => match e {
536
- NpError::Connect(_) => ClientState::NotFound,
537
- NpError::AuthFailed => ClientState::AuthFailed,
538
- NpError::NoProfile => ClientState::Incompatible,
539
- NpError::Incompatible(_) => ClientState::Incompatible,
540
- _ => ClientState::Disconnected,
541
- },
542
- }
543
- }
544
-
545
- /// Reconnect-driven recovery for a single-item raw call.
546
- /// Ordinary failures retry once. Overflow-driven resize recovery may
547
- /// reconnect more than once while negotiated capacities grow.
548
- fn raw_call_with_retry<'a>(
549
- &mut self,
550
- method_code: u16,
551
- request_payload: &[u8],
552
- ) -> Result<ClientResponseRef, NipcError> {
553
- if self.state != ClientState::Ready {
554
- self.error_count += 1;
555
- return Err(NipcError::BadLayout);
556
- }
557
-
558
- // Cap overflow-driven retries: payloads grow by powers of 2, so 8
559
- // retries allows ~256x growth from the initial negotiated size.
560
- let mut overflow_retries = 0u32;
561
- loop {
562
- let prev_req = self.session_max_request_payload_bytes();
563
- let prev_resp = self.session_max_response_payload_bytes();
564
- let prev_cfg_req = self.transport_config.max_request_payload_bytes;
565
- let prev_cfg_resp = self.transport_config.max_response_payload_bytes;
566
-
567
- match self.do_raw_call(method_code, request_payload) {
568
- Ok(payload) => {
569
- self.call_count += 1;
570
- return Ok(payload);
571
- }
572
- Err(first_err) => {
573
- if first_err != NipcError::Overflow {
574
- self.disconnect();
575
- self.state = ClientState::Broken;
576
- self.state = self.try_connect();
577
- if self.state != ClientState::Ready {
578
- self.error_count += 1;
579
- return Err(first_err);
580
- }
581
- self.reconnect_count += 1;
582
-
583
- match self.do_raw_call(method_code, request_payload) {
584
- Ok(payload) => {
585
- self.call_count += 1;
586
- return Ok(payload);
587
- }
588
- Err(retry_err) => {
589
- self.disconnect();
590
- self.state = ClientState::Broken;
591
- self.error_count += 1;
592
- return Err(retry_err);
593
- }
594
- }
595
- }
596
-
597
- self.disconnect();
598
- self.state = ClientState::Broken;
599
- self.state = self.try_connect();
600
- if self.state != ClientState::Ready {
601
- self.error_count += 1;
602
- return Err(first_err);
603
- }
604
- self.reconnect_count += 1;
605
-
606
- if self.session_max_request_payload_bytes() <= prev_req
607
- && self.session_max_response_payload_bytes() <= prev_resp
608
- && self.transport_config.max_request_payload_bytes <= prev_cfg_req
609
- && self.transport_config.max_response_payload_bytes <= prev_cfg_resp
610
- {
611
- self.disconnect();
612
- self.state = ClientState::Broken;
613
- self.error_count += 1;
614
- return Err(first_err);
615
- }
616
-
617
- overflow_retries += 1;
618
- if overflow_retries >= 8 {
619
- self.disconnect();
620
- self.state = ClientState::Broken;
621
- self.error_count += 1;
622
- return Err(first_err);
623
- }
624
- }
625
- }
626
- }
627
- }
628
-
629
- fn raw_call_with_retry_request_buf<'a>(
630
- &mut self,
631
- method_code: u16,
632
- req_len: usize,
633
- ) -> Result<ClientResponseRef, NipcError> {
634
- if self.state != ClientState::Ready {
635
- self.error_count += 1;
636
- return Err(NipcError::BadLayout);
637
- }
638
-
639
- let mut overflow_retries = 0u32;
640
- loop {
641
- let prev_req = self.session_max_request_payload_bytes();
642
- let prev_resp = self.session_max_response_payload_bytes();
643
- let prev_cfg_req = self.transport_config.max_request_payload_bytes;
644
- let prev_cfg_resp = self.transport_config.max_response_payload_bytes;
645
-
646
- match self.do_raw_call_from_request_buf(method_code, req_len) {
647
- Ok(payload) => {
648
- self.call_count += 1;
649
- return Ok(payload);
650
- }
651
- Err(first_err) => {
652
- if first_err != NipcError::Overflow {
653
- self.disconnect();
654
- self.state = ClientState::Broken;
655
- self.state = self.try_connect();
656
- if self.state != ClientState::Ready {
657
- self.error_count += 1;
658
- return Err(first_err);
659
- }
660
- self.reconnect_count += 1;
661
-
662
- match self.do_raw_call_from_request_buf(method_code, req_len) {
663
- Ok(payload) => {
664
- self.call_count += 1;
665
- return Ok(payload);
666
- }
667
- Err(retry_err) => {
668
- self.disconnect();
669
- self.state = ClientState::Broken;
670
- self.error_count += 1;
671
- return Err(retry_err);
672
- }
673
- }
674
- }
675
-
676
- self.disconnect();
677
- self.state = ClientState::Broken;
678
- self.state = self.try_connect();
679
- if self.state != ClientState::Ready {
680
- self.error_count += 1;
681
- return Err(first_err);
682
- }
683
- self.reconnect_count += 1;
684
-
685
- if self.session_max_request_payload_bytes() <= prev_req
686
- && self.session_max_response_payload_bytes() <= prev_resp
687
- && self.transport_config.max_request_payload_bytes <= prev_cfg_req
688
- && self.transport_config.max_response_payload_bytes <= prev_cfg_resp
689
- {
690
- self.disconnect();
691
- self.state = ClientState::Broken;
692
- self.error_count += 1;
693
- return Err(first_err);
694
- }
695
-
696
- overflow_retries += 1;
697
- if overflow_retries >= 8 {
698
- self.disconnect();
699
- self.state = ClientState::Broken;
700
- self.error_count += 1;
701
- return Err(first_err);
702
- }
703
- }
704
- }
705
- }
706
- }
707
-
708
- fn raw_batch_call_with_retry_request_buf<'a>(
709
- &mut self,
710
- method_code: u16,
711
- req_len: usize,
712
- item_count: u32,
713
- ) -> Result<ClientResponseRef, NipcError> {
714
- if self.state != ClientState::Ready {
715
- self.error_count += 1;
716
- return Err(NipcError::BadLayout);
717
- }
718
-
719
- let mut overflow_retries = 0u32;
720
- loop {
721
- let prev_req = self.session_max_request_payload_bytes();
722
- let prev_resp = self.session_max_response_payload_bytes();
723
- let prev_cfg_req = self.transport_config.max_request_payload_bytes;
724
- let prev_cfg_resp = self.transport_config.max_response_payload_bytes;
725
-
726
- match self.do_raw_batch_call_from_request_buf(method_code, req_len, item_count) {
727
- Ok(payload) => {
728
- self.call_count += 1;
729
- return Ok(payload);
730
- }
731
- Err(first_err) => {
732
- if first_err != NipcError::Overflow {
733
- self.disconnect();
734
- self.state = ClientState::Broken;
735
- self.state = self.try_connect();
736
- if self.state != ClientState::Ready {
737
- self.error_count += 1;
738
- return Err(first_err);
739
- }
740
- self.reconnect_count += 1;
741
-
742
- match self.do_raw_batch_call_from_request_buf(
743
- method_code,
744
- req_len,
745
- item_count,
746
- ) {
747
- Ok(payload) => {
748
- self.call_count += 1;
749
- return Ok(payload);
750
- }
751
- Err(retry_err) => {
752
- self.disconnect();
753
- self.state = ClientState::Broken;
754
- self.error_count += 1;
755
- return Err(retry_err);
756
- }
757
- }
758
- }
759
-
760
- self.disconnect();
761
- self.state = ClientState::Broken;
762
- self.state = self.try_connect();
763
- if self.state != ClientState::Ready {
764
- self.error_count += 1;
765
- return Err(first_err);
766
- }
767
- self.reconnect_count += 1;
768
-
769
- if self.session_max_request_payload_bytes() <= prev_req
770
- && self.session_max_response_payload_bytes() <= prev_resp
771
- && self.transport_config.max_request_payload_bytes <= prev_cfg_req
772
- && self.transport_config.max_response_payload_bytes <= prev_cfg_resp
773
- {
774
- self.disconnect();
775
- self.state = ClientState::Broken;
776
- self.error_count += 1;
777
- return Err(first_err);
778
- }
779
-
780
- overflow_retries += 1;
781
- if overflow_retries >= 8 {
782
- self.disconnect();
783
- self.state = ClientState::Broken;
784
- self.error_count += 1;
785
- return Err(first_err);
786
- }
787
- }
788
- }
789
- }
790
- }
791
-
792
- /// Single attempt at a raw call for any method.
793
- fn do_raw_call(
794
- &mut self,
795
- method_code: u16,
796
- request_payload: &[u8],
797
- ) -> Result<ClientResponseRef, NipcError> {
798
- // 1. Build outer header
799
- let mut hdr = Header {
800
- kind: KIND_REQUEST,
801
- code: method_code,
802
- flags: 0,
803
- item_count: 1,
804
- message_id: (self.call_count as u64) + 1,
805
- transport_status: STATUS_OK,
806
- ..Header::default()
807
- };
808
-
809
- // 2. Send via L1 (SHM or UDS)
810
- self.transport_send(&mut hdr, request_payload)?;
811
-
812
- // 3. Receive via L1
813
- let (resp_hdr, response) = self.transport_receive()?;
814
-
815
- // 4. Verify response envelope fields before decode
816
- if resp_hdr.kind != KIND_RESPONSE {
817
- return Err(NipcError::BadKind);
818
- }
819
- if resp_hdr.code != method_code {
820
- return Err(NipcError::BadLayout);
821
- }
822
- if resp_hdr.message_id != hdr.message_id {
823
- return Err(NipcError::BadLayout);
824
- }
825
-
826
- // 5. Check transport_status BEFORE decode (spec requirement)
827
- match resp_hdr.transport_status {
828
- STATUS_OK => {}
829
- STATUS_LIMIT_EXCEEDED => {
830
- let current = self.session_max_response_payload_bytes();
831
- if current > 0 {
832
- self.client_note_response_capacity(current.saturating_mul(2));
833
- }
834
- return Err(NipcError::Overflow);
835
- }
836
- _ => return Err(NipcError::BadLayout),
837
- }
838
- Ok(response)
839
- }
840
-
841
- fn do_raw_call_from_request_buf(
842
- &mut self,
843
- method_code: u16,
844
- req_len: usize,
845
- ) -> Result<ClientResponseRef, NipcError> {
846
- let mut hdr = Header {
847
- kind: KIND_REQUEST,
848
- code: method_code,
849
- flags: 0,
850
- item_count: 1,
851
- message_id: (self.call_count as u64) + 1,
852
- transport_status: STATUS_OK,
853
- ..Header::default()
854
- };
855
-
856
- self.transport_send_request_buf(&mut hdr, req_len)?;
857
- let (resp_hdr, response) = self.transport_receive()?;
858
-
859
- if resp_hdr.kind != KIND_RESPONSE {
860
- return Err(NipcError::BadKind);
861
- }
862
- if resp_hdr.code != method_code {
863
- return Err(NipcError::BadLayout);
864
- }
865
- if resp_hdr.message_id != hdr.message_id {
866
- return Err(NipcError::BadLayout);
867
- }
868
- match resp_hdr.transport_status {
869
- STATUS_OK => {}
870
- STATUS_LIMIT_EXCEEDED => {
871
- let current = self.session_max_response_payload_bytes();
872
- if current > 0 {
873
- self.client_note_response_capacity(current.saturating_mul(2));
874
- }
875
- return Err(NipcError::Overflow);
876
- }
877
- _ => return Err(NipcError::BadLayout),
878
- }
879
- Ok(response)
880
- }
881
-
882
- /// Single attempt at a raw batch call. Like `do_raw_call` but sets
883
- /// FLAG_BATCH and item_count, and validates the response matches.
884
- fn do_raw_batch_call_from_request_buf(
885
- &mut self,
886
- method_code: u16,
887
- req_len: usize,
888
- item_count: u32,
889
- ) -> Result<ClientResponseRef, NipcError> {
890
- let mut hdr = Header {
891
- kind: KIND_REQUEST,
892
- code: method_code,
893
- flags: FLAG_BATCH,
894
- item_count,
895
- message_id: (self.call_count as u64) + 1,
896
- transport_status: STATUS_OK,
897
- ..Header::default()
898
- };
899
-
900
- self.transport_send_request_buf(&mut hdr, req_len)?;
901
-
902
- let (resp_hdr, response) = self.transport_receive()?;
903
-
904
- if resp_hdr.kind != KIND_RESPONSE {
905
- return Err(NipcError::BadKind);
906
- }
907
- if resp_hdr.code != method_code {
908
- return Err(NipcError::BadLayout);
909
- }
910
- if resp_hdr.message_id != hdr.message_id {
911
- return Err(NipcError::BadLayout);
912
- }
913
- match resp_hdr.transport_status {
914
- STATUS_OK => {}
915
- STATUS_LIMIT_EXCEEDED => {
916
- let current = self.session_max_response_payload_bytes();
917
- if current > 0 {
918
- self.client_note_response_capacity(current.saturating_mul(2));
919
- }
920
- return Err(NipcError::Overflow);
921
- }
922
- _ => return Err(NipcError::BadLayout),
923
- }
924
- if resp_hdr.item_count != item_count {
925
- return Err(NipcError::BadItemCount);
926
- }
927
- Ok(response)
928
- }
929
-
930
- /// Send via the active transport (SHM if available, baseline otherwise).
931
- fn transport_send(&mut self, hdr: &mut Header, payload: &[u8]) -> Result<(), NipcError> {
932
- let max_request_payload_bytes = self.session_max_request_payload_bytes();
933
-
934
- // SHM path (POSIX or Windows)
935
- #[cfg(target_os = "linux")]
936
- {
937
- if let Some(ref mut shm) = self.shm {
938
- if payload.len() > max_request_payload_bytes as usize {
939
- self.client_note_request_capacity(payload.len() as u32);
940
- return Err(NipcError::Overflow);
941
- }
942
-
943
- let msg_len = HEADER_SIZE + payload.len();
944
- let msg = ensure_client_scratch(&mut self.send_buf, msg_len);
945
-
946
- hdr.magic = MAGIC_MSG;
947
- hdr.version = VERSION;
948
- hdr.header_len = protocol::HEADER_LEN;
949
- hdr.payload_len = payload.len() as u32;
950
-
951
- hdr.encode(&mut msg[..HEADER_SIZE]);
952
- if !payload.is_empty() {
953
- msg[HEADER_SIZE..].copy_from_slice(payload);
954
- }
955
-
956
- let send_result = shm.send(&msg);
957
- return match send_result {
958
- Ok(()) => Ok(()),
959
- Err(crate::transport::shm::ShmError::MsgTooLarge) => {
960
- self.client_note_request_capacity(payload.len() as u32);
961
- Err(NipcError::Overflow)
962
- }
963
- Err(_) => Err(NipcError::Truncated),
964
- };
965
- }
966
- }
967
-
968
- #[cfg(windows)]
969
- {
970
- if let Some(ref mut shm) = self.shm {
971
- if payload.len() > max_request_payload_bytes as usize {
972
- self.client_note_request_capacity(payload.len() as u32);
973
- return Err(NipcError::Overflow);
974
- }
975
-
976
- let msg_len = HEADER_SIZE + payload.len();
977
- let msg = ensure_client_scratch(&mut self.send_buf, msg_len);
978
-
979
- hdr.magic = MAGIC_MSG;
980
- hdr.version = VERSION;
981
- hdr.header_len = protocol::HEADER_LEN;
982
- hdr.payload_len = payload.len() as u32;
983
-
984
- hdr.encode(&mut msg[..HEADER_SIZE]);
985
- if !payload.is_empty() {
986
- msg[HEADER_SIZE..].copy_from_slice(payload);
987
- }
988
-
989
- let send_result = shm.send(&msg);
990
- return match send_result {
991
- Ok(()) => Ok(()),
992
- Err(crate::transport::win_shm::WinShmError::MsgTooLarge) => {
993
- self.client_note_request_capacity(payload.len() as u32);
994
- Err(NipcError::Overflow)
995
- }
996
- Err(_) => Err(NipcError::Truncated),
997
- };
998
- }
999
- }
1000
-
1001
- // Baseline transport path
1002
- let send_result = {
1003
- let session = self.session.as_mut().ok_or(NipcError::Truncated)?;
1004
- session.send(hdr, payload)
1005
- };
1006
- match send_result {
1007
- Ok(()) => Ok(()),
1008
- #[cfg(unix)]
1009
- Err(crate::transport::posix::UdsError::LimitExceeded) => {
1010
- self.client_note_request_capacity(payload.len() as u32);
1011
- Err(NipcError::Overflow)
1012
- }
1013
- #[cfg(windows)]
1014
- Err(crate::transport::windows::NpError::LimitExceeded) => {
1015
- self.client_note_request_capacity(payload.len() as u32);
1016
- Err(NipcError::Overflow)
1017
- }
1018
- Err(_) => Err(NipcError::Truncated),
1019
- }
1020
- }
1021
-
1022
- fn transport_send_request_buf(
1023
- &mut self,
1024
- hdr: &mut Header,
1025
- req_len: usize,
1026
- ) -> Result<(), NipcError> {
1027
- let max_request_payload_bytes = self.session_max_request_payload_bytes();
1028
-
1029
- #[cfg(target_os = "linux")]
1030
- {
1031
- if let Some(ref mut shm) = self.shm {
1032
- if req_len > max_request_payload_bytes as usize {
1033
- self.client_note_request_capacity(req_len as u32);
1034
- return Err(NipcError::Overflow);
1035
- }
1036
-
1037
- let msg_len = HEADER_SIZE + req_len;
1038
- let msg = ensure_client_scratch(&mut self.send_buf, msg_len);
1039
-
1040
- hdr.magic = MAGIC_MSG;
1041
- hdr.version = VERSION;
1042
- hdr.header_len = protocol::HEADER_LEN;
1043
- hdr.payload_len = req_len as u32;
1044
-
1045
- hdr.encode(&mut msg[..HEADER_SIZE]);
1046
- if req_len > 0 {
1047
- msg[HEADER_SIZE..HEADER_SIZE + req_len]
1048
- .copy_from_slice(&self.request_buf[..req_len]);
1049
- }
1050
-
1051
- let send_result = shm.send(&msg[..msg_len]);
1052
- return match send_result {
1053
- Ok(()) => Ok(()),
1054
- Err(crate::transport::shm::ShmError::MsgTooLarge) => {
1055
- self.client_note_request_capacity(req_len as u32);
1056
- Err(NipcError::Overflow)
1057
- }
1058
- Err(_) => Err(NipcError::Truncated),
1059
- };
1060
- }
1061
- }
1062
-
1063
- #[cfg(windows)]
1064
- {
1065
- if let Some(ref mut shm) = self.shm {
1066
- if req_len > max_request_payload_bytes as usize {
1067
- self.client_note_request_capacity(req_len as u32);
1068
- return Err(NipcError::Overflow);
1069
- }
1070
-
1071
- let msg_len = HEADER_SIZE + req_len;
1072
- let msg = ensure_client_scratch(&mut self.send_buf, msg_len);
1073
-
1074
- hdr.magic = MAGIC_MSG;
1075
- hdr.version = VERSION;
1076
- hdr.header_len = protocol::HEADER_LEN;
1077
- hdr.payload_len = req_len as u32;
1078
-
1079
- hdr.encode(&mut msg[..HEADER_SIZE]);
1080
- if req_len > 0 {
1081
- msg[HEADER_SIZE..HEADER_SIZE + req_len]
1082
- .copy_from_slice(&self.request_buf[..req_len]);
1083
- }
1084
-
1085
- let send_result = shm.send(&msg[..msg_len]);
1086
- return match send_result {
1087
- Ok(()) => Ok(()),
1088
- Err(crate::transport::win_shm::WinShmError::MsgTooLarge) => {
1089
- self.client_note_request_capacity(req_len as u32);
1090
- Err(NipcError::Overflow)
1091
- }
1092
- Err(_) => Err(NipcError::Truncated),
1093
- };
1094
- }
1095
- }
1096
-
1097
- let send_result = {
1098
- let session = self.session.as_mut().ok_or(NipcError::Truncated)?;
1099
- session.send(hdr, &self.request_buf[..req_len])
1100
- };
1101
- match send_result {
1102
- Ok(()) => Ok(()),
1103
- #[cfg(unix)]
1104
- Err(crate::transport::posix::UdsError::LimitExceeded) => {
1105
- self.client_note_request_capacity(req_len as u32);
1106
- Err(NipcError::Overflow)
1107
- }
1108
- #[cfg(windows)]
1109
- Err(crate::transport::windows::NpError::LimitExceeded) => {
1110
- self.client_note_request_capacity(req_len as u32);
1111
- Err(NipcError::Overflow)
1112
- }
1113
- Err(_) => Err(NipcError::Truncated),
1114
- }
1115
- }
1116
-
1117
- /// Receive via the active transport. Returns (header, payload_view).
1118
- fn transport_receive(&mut self) -> Result<(Header, ClientResponseRef), NipcError> {
1119
- let needed = self.max_receive_message_bytes();
1120
- let scratch = ensure_client_scratch(&mut self.transport_buf, needed);
1121
-
1122
- // SHM path (POSIX or Windows)
1123
- #[cfg(target_os = "linux")]
1124
- {
1125
- if let Some(ref mut shm) = self.shm {
1126
- let mlen = shm
1127
- .receive(scratch, 30000)
1128
- .map_err(|_| NipcError::Truncated)?;
1129
-
1130
- if mlen < HEADER_SIZE {
1131
- return Err(NipcError::Truncated);
1132
- }
1133
-
1134
- let hdr = Header::decode(&scratch[..mlen])?;
1135
- return Ok((
1136
- hdr,
1137
- ClientResponseRef {
1138
- source: ClientResponseSource::TransportBuf,
1139
- len: mlen - HEADER_SIZE,
1140
- },
1141
- ));
1142
- }
1143
- }
1144
-
1145
- #[cfg(windows)]
1146
- {
1147
- if let Some(ref mut shm) = self.shm {
1148
- let mlen = shm
1149
- .receive(scratch, 30000)
1150
- .map_err(|_| NipcError::Truncated)?;
1151
-
1152
- if mlen < HEADER_SIZE {
1153
- return Err(NipcError::Truncated);
1154
- }
1155
-
1156
- let hdr = Header::decode(&scratch[..mlen])?;
1157
- return Ok((
1158
- hdr,
1159
- ClientResponseRef {
1160
- source: ClientResponseSource::TransportBuf,
1161
- len: mlen - HEADER_SIZE,
1162
- },
1163
- ));
1164
- }
1165
- }
1166
-
1167
- // Baseline transport: UDS on POSIX, Named Pipe on Windows
1168
- let session = self.session.as_mut().ok_or(NipcError::Truncated)?;
1169
-
1170
- #[cfg(unix)]
1171
- {
1172
- let scratch_payload_ptr = unsafe { scratch.as_ptr().add(HEADER_SIZE) };
1173
- let (hdr, payload) = session.receive(scratch).map_err(|_| NipcError::Truncated)?;
1174
- let source = if payload.as_ptr() == scratch_payload_ptr {
1175
- ClientResponseSource::TransportBuf
1176
- } else {
1177
- ClientResponseSource::SessionBuf
1178
- };
1179
- Ok((
1180
- hdr,
1181
- ClientResponseRef {
1182
- source,
1183
- len: payload.len(),
1184
- },
1185
- ))
1186
- }
1187
-
1188
- #[cfg(windows)]
1189
- {
1190
- let scratch_payload_ptr = unsafe { scratch.as_ptr().add(HEADER_SIZE) };
1191
- let (hdr, payload) = session.receive(scratch).map_err(|_| NipcError::Truncated)?;
1192
- let source = if payload.as_ptr() == scratch_payload_ptr {
1193
- ClientResponseSource::TransportBuf
1194
- } else {
1195
- ClientResponseSource::SessionBuf
1196
- };
1197
- Ok((
1198
- hdr,
1199
- ClientResponseRef {
1200
- source,
1201
- len: payload.len(),
1202
- },
1203
- ))
1204
- }
1205
- }
1206
-
1207
- fn response_payload(&self, response: ClientResponseRef) -> Result<&[u8], NipcError> {
1208
- match response.source {
1209
- ClientResponseSource::TransportBuf => {
1210
- let start = HEADER_SIZE;
1211
- let end = HEADER_SIZE + response.len;
1212
- if end > self.transport_buf.len() {
1213
- return Err(NipcError::Truncated);
1214
- }
1215
- Ok(&self.transport_buf[start..end])
1216
- }
1217
- ClientResponseSource::SessionBuf => {
1218
- #[cfg(unix)]
1219
- {
1220
- let session = self.session.as_ref().ok_or(NipcError::Truncated)?;
1221
- return Ok(session.received_payload(response.len));
1222
- }
1223
- #[cfg(windows)]
1224
- {
1225
- let session = self.session.as_ref().ok_or(NipcError::Truncated)?;
1226
- return Ok(session.received_payload(response.len));
1227
- }
1228
- #[allow(unreachable_code)]
1229
- Err(NipcError::Truncated)
1230
- }
1231
- }
1232
- }
1233
-
1234
- fn max_receive_message_bytes(&self) -> usize {
1235
- let mut max_payload = self.transport_config.max_response_payload_bytes as usize;
1236
- #[cfg(unix)]
1237
- if let Some(ref session) = self.session {
1238
- if session.max_response_payload_bytes > 0 {
1239
- max_payload = session.max_response_payload_bytes as usize;
1240
- }
1241
- }
1242
- #[cfg(windows)]
1243
- if let Some(ref session) = self.session {
1244
- if session.max_response_payload_bytes > 0 {
1245
- max_payload = session.max_response_payload_bytes as usize;
1246
- }
1247
- }
1248
- if max_payload == 0 {
1249
- max_payload = CACHE_RESPONSE_BUF_SIZE;
1250
- }
1251
- HEADER_SIZE + max_payload
1252
- }
1253
-}
1254
-
1255
-impl Drop for RawClient {
1256
- fn drop(&mut self) {
1257
- self.close();
1258
- }
1259
-}
1260
-
1261
-fn ensure_client_scratch(buf: &mut Vec<u8>, needed: usize) -> &mut [u8] {
1262
- if buf.len() < needed {
1263
- buf.resize(needed, 0);
1264
- }
1265
- &mut buf[..needed]
1266
-}
1267
-
1268
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1269
-enum ClientResponseSource {
1270
- TransportBuf,
1271
- SessionBuf,
1272
-}
1273
-
1274
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1275
-struct ClientResponseRef {
1276
- source: ClientResponseSource,
1277
- len: usize,
1278
-}
1279
-
1280
-fn dispatch_single_internal(
1281
- expected_method_code: u16,
1282
- handler: Option<&DispatchHandler>,
1283
- method_code: u16,
1284
- request: &[u8],
1285
- response_buf: &mut [u8],
1286
-) -> Result<usize, DispatchError> {
1287
- if method_code != expected_method_code {
1288
- return Err(DispatchError::HandlerFailed);
1289
- }
1290
-
1291
- match handler {
1292
- Some(dispatch) => match dispatch(request, response_buf) {
1293
- Ok(n) if n <= response_buf.len() => Ok(n),
1294
- Ok(_) => Err(DispatchError::Overflow),
1295
- Err(err) => Err(err),
1296
- },
1297
- None => Err(DispatchError::HandlerFailed),
1298
- }
1299
-}
1300
-
1301
-#[cfg(test)]
1302
-#[allow(dead_code)]
1303
-fn dispatch_single(
1304
- expected_method_code: u16,
1305
- handler: Option<&DispatchHandler>,
1306
- method_code: u16,
1307
- request: &[u8],
1308
- response_buf: &mut [u8],
1309
-) -> Result<usize, DispatchError> {
1310
- dispatch_single_internal(
1311
- expected_method_code,
1312
- handler,
1313
- method_code,
1314
- request,
1315
- response_buf,
1316
- )
1317
-}
1318
-
1319
-fn method_supported_internal(
1320
- expected_method_code: u16,
1321
- handler: Option<&DispatchHandler>,
1322
- method_code: u16,
1323
-) -> bool {
1324
- handler.is_some() && method_code == expected_method_code
1325
-}
1326
-
1327
-fn server_note_payload_capacity(target: &AtomicU32, payload_len: u32) {
1328
- let grown = next_power_of_2_u32(payload_len);
1329
- let mut current = target.load(Ordering::Relaxed);
1330
- while grown > current {
1331
- match target.compare_exchange_weak(current, grown, Ordering::Release, Ordering::Relaxed) {
1332
- Ok(_) => break,
1333
- Err(observed) => current = observed,
1334
- }
1335
- }
1336
-}
1337
-
1338
-// ---------------------------------------------------------------------------
1339
-// Managed server
1340
-// ---------------------------------------------------------------------------
1341
-
1342
-pub type IncrementHandler = Arc<dyn Fn(u64) -> Option<u64> + Send + Sync>;
1343
-pub type StringReverseHandler = Arc<dyn Fn(&str) -> Option<String> + Send + Sync>;
1344
-pub type SnapshotHandler =
1345
- Arc<dyn for<'a> Fn(&CgroupsRequest, &mut protocol::CgroupsBuilder<'a>) -> bool + Send + Sync>;
1346
-
1347
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1348
-pub enum DispatchError {
1349
- BadEnvelope,
1350
- Overflow,
1351
- HandlerFailed,
1352
-}
1353
-
1354
-pub type DispatchHandler =
1355
- Arc<dyn Fn(&[u8], &mut [u8]) -> Result<usize, DispatchError> + Send + Sync>;
1356
-
1357
-pub fn increment_dispatch(handler: IncrementHandler) -> DispatchHandler {
1358
- Arc::new(move |request, response_buf| {
1359
- let value = increment_decode(request).map_err(|_| DispatchError::BadEnvelope)?;
1360
- let result = handler(value).ok_or(DispatchError::HandlerFailed)?;
1361
- let n = increment_encode(result, response_buf);
1362
- if n == 0 {
1363
- return Err(DispatchError::Overflow);
1364
- }
1365
- Ok(n)
1366
- })
1367
-}
1368
-
1369
-pub fn string_reverse_dispatch(handler: StringReverseHandler) -> DispatchHandler {
1370
- Arc::new(move |request, response_buf| {
1371
- let view = string_reverse_decode(request).map_err(|_| DispatchError::BadEnvelope)?;
1372
- let result = handler(view.as_str()).ok_or(DispatchError::HandlerFailed)?;
1373
- let n = string_reverse_encode(result.as_bytes(), response_buf);
1374
- if n == 0 {
1375
- return Err(DispatchError::Overflow);
1376
- }
1377
- Ok(n)
1378
- })
1379
-}
1380
-
1381
-pub fn snapshot_max_items(response_buf_size: usize, override_max_items: u32) -> u32 {
1382
- if override_max_items != 0 {
1383
- return override_max_items;
1384
- }
1385
- protocol::estimate_cgroups_max_items(response_buf_size)
1386
-}
1387
-
1388
-pub fn snapshot_dispatch(handler: SnapshotHandler, max_items: u32) -> DispatchHandler {
1389
- Arc::new(move |request, response_buf| {
1390
- let request = CgroupsRequest::decode(request).map_err(|_| DispatchError::BadEnvelope)?;
1391
- let item_budget = snapshot_max_items(response_buf.len(), max_items);
1392
- if item_budget == 0 {
1393
- return Err(DispatchError::Overflow);
1394
- }
1395
- let mut builder = protocol::CgroupsBuilder::new(response_buf, item_budget, 0, 0);
1396
- if !handler(&request, &mut builder) {
1397
- return Err(DispatchError::HandlerFailed);
1398
- }
1399
- let n = builder.finish();
1400
- if n == 0 {
1401
- return Err(DispatchError::Overflow);
1402
- }
1403
- Ok(n)
1404
- })
1405
-}
1406
-
1407
-/// L2 managed server. Typed request/response dispatcher.
1408
-///
1409
-/// Handles accept, spawns a thread per session (up to worker_count),
1410
-/// reads requests, dispatches to handler, sends responses.
1411
-pub struct ManagedServer {
1412
- run_dir: String,
1413
- service_name: String,
1414
- server_config: ServerConfig,
1415
- expected_method_code: u16,
1416
- handler: Option<DispatchHandler>,
1417
- running: Arc<AtomicBool>,
1418
- learned_request_payload_bytes: Arc<AtomicU32>,
1419
- learned_response_payload_bytes: Arc<AtomicU32>,
1420
- next_session_id: u64,
1421
- worker_count: usize,
1422
- /// Windows: stored listener handle so stop() can close it to unblock Accept.
1423
- #[cfg(windows)]
1424
- listener_handle: Arc<std::sync::Mutex<Option<usize>>>,
1425
-}
1426
-
1427
-impl ManagedServer {
1428
- /// Create a new managed server for a single service kind.
1429
- pub fn new(
1430
- run_dir: &str,
1431
- service_name: &str,
1432
- config: ServerConfig,
1433
- expected_method_code: u16,
1434
- handler: Option<DispatchHandler>,
1435
- ) -> Self {
1436
- Self::with_workers(
1437
- run_dir,
1438
- service_name,
1439
- config,
1440
- expected_method_code,
1441
- handler,
1442
- 8,
1443
- )
1444
- }
1445
-
1446
- /// Create a managed server with an explicit worker count.
1447
- pub fn with_workers(
1448
- run_dir: &str,
1449
- service_name: &str,
1450
- config: ServerConfig,
1451
- expected_method_code: u16,
1452
- handler: Option<DispatchHandler>,
1453
- worker_count: usize,
1454
- ) -> Self {
1455
- let learned_request = if config.max_request_payload_bytes != 0 {
1456
- config.max_request_payload_bytes
1457
- } else {
1458
- MAX_PAYLOAD_DEFAULT
1459
- };
1460
- let learned_response = if config.max_response_payload_bytes != 0 {
1461
- config.max_response_payload_bytes
1462
- } else {
1463
- MAX_PAYLOAD_DEFAULT
1464
- };
1465
-
1466
- ManagedServer {
1467
- run_dir: run_dir.to_string(),
1468
- service_name: service_name.to_string(),
1469
- server_config: config,
1470
- expected_method_code,
1471
- handler,
1472
- running: Arc::new(AtomicBool::new(false)),
1473
- learned_request_payload_bytes: Arc::new(AtomicU32::new(learned_request)),
1474
- learned_response_payload_bytes: Arc::new(AtomicU32::new(learned_response)),
1475
- next_session_id: 1,
1476
- worker_count: if worker_count < 1 { 1 } else { worker_count },
1477
- #[cfg(windows)]
1478
- listener_handle: Arc::new(std::sync::Mutex::new(None)),
1479
- }
1480
- }
1481
-
1482
- /// Run the acceptor loop. Blocking. Accepts clients, spawns a
1483
- /// thread per session (up to worker_count concurrent sessions).
1484
- ///
1485
- /// Returns when `stop()` is called or on fatal error.
1486
- #[cfg(unix)]
1487
- pub fn run(&mut self) -> Result<(), NipcError> {
1488
- #[cfg(target_os = "linux")]
1489
- crate::transport::shm::cleanup_stale(&self.run_dir, &self.service_name);
1490
-
1491
- let listener = UdsListener::bind(
1492
- &self.run_dir,
1493
- &self.service_name,
1494
- self.server_config.clone(),
1495
- )
1496
- .map_err(|_| NipcError::BadLayout)?;
1497
-
1498
- self.running.store(true, Ordering::Release);
1499
-
1500
- let mut session_threads: Vec<std::thread::JoinHandle<()>> = Vec::new();
1501
-
1502
- while self.running.load(Ordering::Acquire) {
1503
- let ready = poll_fd(listener.fd(), SERVER_POLL_TIMEOUT_MS as i32);
1504
- if ready < 0 {
1505
- break;
1506
- }
1507
- if ready == 0 {
1508
- // Reap finished threads periodically
1509
- session_threads.retain(|t| !t.is_finished());
1510
- continue;
1511
- }
1512
-
1513
- let (session_id, accept_cfg, precreated_shm, ready) = self.prepare_unix_accept();
1514
- if !ready {
1515
- std::thread::sleep(std::time::Duration::from_millis(10));
1516
- continue;
1517
- }
1518
-
1519
- let session = match listener.accept_with_config(session_id, accept_cfg) {
1520
- Ok(s) => s,
1521
- Err(_) => {
1522
- #[cfg(target_os = "linux")]
1523
- if let Some(mut shm) = precreated_shm {
1524
- shm.destroy();
1525
- }
1526
- if !self.running.load(Ordering::Acquire) {
1527
- break;
1528
- }
1529
- std::thread::sleep(std::time::Duration::from_millis(10));
1530
- continue;
1531
- }
1532
- };
1533
-
1534
- // Check worker count limit (non-blocking)
1535
- // Reap finished threads first
1536
- session_threads.retain(|t| !t.is_finished());
1537
- if session_threads.len() >= self.worker_count {
1538
- // At capacity: reject client
1539
- #[cfg(target_os = "linux")]
1540
- if let Some(mut shm) = precreated_shm {
1541
- shm.destroy();
1542
- }
1543
- drop(session);
1544
- continue;
1545
- }
1546
-
1547
- #[cfg(target_os = "linux")]
1548
- let shm = match self.finalize_unix_shm(&session, precreated_shm) {
1549
- Some(shm) => Some(shm),
1550
- None if session.selected_profile == PROFILE_SHM_HYBRID
1551
- || session.selected_profile == PROFILE_SHM_FUTEX =>
1552
- {
1553
- drop(session);
1554
- continue;
1555
- }
1556
- None => None,
1557
- };
1558
- #[cfg(not(target_os = "linux"))]
1559
- let shm: Option<()> = None;
1560
-
1561
- // Spawn a handler thread for this session
1562
- let expected_method_code = self.expected_method_code;
1563
- let handler = self.handler.clone();
1564
- let running = self.running.clone();
1565
- let learned_request_payload_bytes = self.learned_request_payload_bytes.clone();
1566
- let learned_response_payload_bytes = self.learned_response_payload_bytes.clone();
1567
-
1568
- let t = std::thread::spawn(move || {
1569
- handle_session_threaded(
1570
- session,
1571
- #[cfg(target_os = "linux")]
1572
- shm,
1573
- #[cfg(not(target_os = "linux"))]
1574
- shm,
1575
- expected_method_code,
1576
- handler,
1577
- running,
1578
- learned_request_payload_bytes,
1579
- learned_response_payload_bytes,
1580
- );
1581
- });
1582
- session_threads.push(t);
1583
- }
1584
-
1585
- // Wait for all active session threads
1586
- for t in session_threads {
1587
- let _ = t.join();
1588
- }
1589
-
1590
- Ok(())
1591
- }
1592
-
1593
- /// Windows: run the acceptor loop over Named Pipes.
1594
- #[cfg(windows)]
1595
- pub fn run(&mut self) -> Result<(), NipcError> {
1596
- // Win SHM cleanup is a no-op: kernel objects auto-clean on handle close.
1597
-
1598
- let mut listener = NpListener::bind(
1599
- &self.run_dir,
1600
- &self.service_name,
1601
- self.server_config.clone(),
1602
- )
1603
- .map_err(|_| NipcError::BadLayout)?;
1604
-
1605
- // Store listener handle so stop() can close it to unblock Accept
1606
- *self.listener_handle.lock().unwrap() = Some(listener.handle() as usize);
1607
-
1608
- self.running.store(true, Ordering::Release);
1609
-
1610
- let mut session_threads: Vec<std::thread::JoinHandle<()>> = Vec::new();
1611
-
1612
- while self.running.load(Ordering::Acquire) {
1613
- let (session_id, accept_cfg, prepared_shm, ready) = self.prepare_windows_accept();
1614
- if !ready {
1615
- std::thread::sleep(std::time::Duration::from_millis(10));
1616
- continue;
1617
- }
1618
-
1619
- let session = match listener.accept_with_config(session_id, accept_cfg) {
1620
- Ok(s) => s,
1621
- Err(_) => {
1622
- if let Some(mut prepared) = prepared_shm {
1623
- prepared.destroy_all();
1624
- }
1625
- if !self.running.load(Ordering::Acquire) {
1626
- break;
1627
- }
1628
- std::thread::sleep(std::time::Duration::from_millis(10));
1629
- continue;
1630
- }
1631
- };
1632
-
1633
- // Reap finished threads
1634
- session_threads.retain(|t| !t.is_finished());
1635
- if session_threads.len() >= self.worker_count {
1636
- if let Some(mut prepared) = prepared_shm {
1637
- prepared.destroy_all();
1638
- }
1639
- drop(session);
1640
- continue;
1641
- }
1642
-
1643
- let shm = match self.finalize_windows_shm(&session, prepared_shm) {
1644
- Some(shm) => Some(shm),
1645
- None if session.selected_profile == WIN_SHM_PROFILE_HYBRID
1646
- || session.selected_profile == WIN_SHM_PROFILE_BUSYWAIT =>
1647
- {
1648
- drop(session);
1649
- continue;
1650
- }
1651
- None => None,
1652
- };
1653
-
1654
- if shm.is_none()
1655
- && (session.selected_profile == WIN_SHM_PROFILE_HYBRID
1656
- || session.selected_profile == WIN_SHM_PROFILE_BUSYWAIT)
1657
- {
1658
- drop(session);
1659
- continue;
1660
- }
1661
-
1662
- let expected_method_code = self.expected_method_code;
1663
- let handler = self.handler.clone();
1664
- let running = self.running.clone();
1665
- let learned_request_payload_bytes = self.learned_request_payload_bytes.clone();
1666
- let learned_response_payload_bytes = self.learned_response_payload_bytes.clone();
1667
- let t = std::thread::spawn(move || {
1668
- handle_session_win_threaded(
1669
- session,
1670
- shm,
1671
- expected_method_code,
1672
- handler,
1673
- running,
1674
- learned_request_payload_bytes,
1675
- learned_response_payload_bytes,
1676
- );
1677
- });
1678
- session_threads.push(t);
1679
- }
1680
-
1681
- for t in session_threads {
1682
- let _ = t.join();
1683
- }
1684
-
1685
- Ok(())
1686
- }
1687
-
1688
- /// Signal shutdown. On Windows, also closes the listener pipe to
1689
- /// unblock ConnectNamedPipe in the accept loop.
1690
- pub fn stop(&self) {
1691
- self.running.store(false, Ordering::Release);
1692
-
1693
- #[cfg(windows)]
1694
- {
1695
- let mut guard = self.listener_handle.lock().unwrap();
1696
- if let Some(h) = guard.take() {
1697
- // Close the listener pipe to unblock ConnectNamedPipe
1698
- extern "system" {
1699
- fn CloseHandle(h: isize) -> i32;
1700
- }
1701
- unsafe {
1702
- CloseHandle(h as isize);
1703
- }
1704
- }
1705
- }
1706
- }
1707
-
1708
- /// Returns the internal running flag for diagnostics and test helpers.
1709
- ///
1710
- /// For reliable shutdown, call `stop()`. On Windows, flipping this flag
1711
- /// alone does not wake a blocking listener accept.
1712
- pub fn running_flag(&self) -> Arc<AtomicBool> {
1713
- self.running.clone()
1714
- }
1715
-
1716
- // ------------------------------------------------------------------
1717
- // Internal helpers
1718
- // ------------------------------------------------------------------
1719
-
1720
- #[cfg(target_os = "linux")]
1721
- fn prepare_unix_accept(&mut self) -> (u64, ServerConfig, Option<ShmContext>, bool) {
1722
- let session_id = self.next_session_id;
1723
- self.next_session_id += 1;
1724
-
1725
- let mut cfg = self.server_config.clone();
1726
- cfg.max_request_payload_bytes = self.learned_request_payload_bytes.load(Ordering::Acquire);
1727
- cfg.max_response_payload_bytes =
1728
- self.learned_response_payload_bytes.load(Ordering::Acquire);
1729
-
1730
- let shm_profiles = cfg.supported_profiles & (PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
1731
- if shm_profiles == 0 {
1732
- return (session_id, cfg, None, true);
1733
- }
1734
-
1735
- match ShmContext::server_create(
1736
- &self.run_dir,
1737
- &self.service_name,
1738
- session_id,
1739
- cfg.max_request_payload_bytes + HEADER_SIZE as u32,
1740
- cfg.max_response_payload_bytes + HEADER_SIZE as u32,
1741
- ) {
1742
- Ok(ctx) => (session_id, cfg, Some(ctx), true),
1743
- Err(_) => {
1744
- cfg.supported_profiles &= !(PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
1745
- cfg.preferred_profiles &= !(PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
1746
- (session_id, cfg.clone(), None, cfg.supported_profiles != 0)
1747
- }
1748
- }
1749
- }
1750
-
1751
- #[cfg(target_os = "linux")]
1752
- fn finalize_unix_shm(
1753
- &self,
1754
- session: &UdsSession,
1755
- mut shm: Option<ShmContext>,
1756
- ) -> Option<ShmContext> {
1757
- let profile = session.selected_profile;
1758
- if profile != PROFILE_SHM_HYBRID && profile != PROFILE_SHM_FUTEX {
1759
- if let Some(ref mut ctx) = shm {
1760
- ctx.destroy();
1761
- }
1762
- return None;
1763
- }
1764
- shm
1765
- }
1766
-
1767
- #[cfg(windows)]
1768
- fn prepare_windows_accept(&mut self) -> (u64, ServerConfig, Option<PreparedWinShm>, bool) {
1769
- let session_id = self.next_session_id;
1770
- self.next_session_id += 1;
1771
-
1772
- let mut cfg = self.server_config.clone();
1773
- cfg.max_request_payload_bytes = self.learned_request_payload_bytes.load(Ordering::Acquire);
1774
- cfg.max_response_payload_bytes =
1775
- self.learned_response_payload_bytes.load(Ordering::Acquire);
1776
-
1777
- let shm_profiles =
1778
- cfg.supported_profiles & (WIN_SHM_PROFILE_HYBRID | WIN_SHM_PROFILE_BUSYWAIT);
1779
- if shm_profiles == 0 {
1780
- return (session_id, cfg, None, true);
1781
- }
1782
-
1783
- let mut prepared = PreparedWinShm::default();
1784
- for profile in [WIN_SHM_PROFILE_HYBRID, WIN_SHM_PROFILE_BUSYWAIT] {
1785
- if cfg.supported_profiles & profile == 0 {
1786
- continue;
1787
- }
1788
-
1789
- match WinShmContext::server_create(
1790
- &self.run_dir,
1791
- &self.service_name,
1792
- self.server_config.auth_token,
1793
- session_id,
1794
- profile,
1795
- cfg.max_request_payload_bytes + HEADER_SIZE as u32,
1796
- cfg.max_response_payload_bytes + HEADER_SIZE as u32,
1797
- ) {
1798
- Ok(ctx) => prepared.insert(profile, ctx),
1799
- Err(_) => {
1800
- cfg.supported_profiles &= !profile;
1801
- cfg.preferred_profiles &= !profile;
1802
- }
1803
- }
1804
- }
1805
-
1806
- if cfg.supported_profiles == 0 {
1807
- prepared.destroy_all();
1808
- return (session_id, cfg, None, false);
1809
- }
1810
-
1811
- if prepared.is_empty() {
1812
- return (session_id, cfg, None, true);
1813
- }
1814
-
1815
- (session_id, cfg, Some(prepared), true)
1816
- }
1817
-
1818
- #[cfg(windows)]
1819
- fn finalize_windows_shm(
1820
- &self,
1821
- session: &NpSession,
1822
- mut prepared: Option<PreparedWinShm>,
1823
- ) -> Option<WinShmContext> {
1824
- let profile = session.selected_profile;
1825
- if profile != WIN_SHM_PROFILE_HYBRID && profile != WIN_SHM_PROFILE_BUSYWAIT {
1826
- if let Some(ref mut prepared) = prepared {
1827
- prepared.destroy_all();
1828
- }
1829
- return None;
1830
- }
1831
- let mut prepared = prepared?;
1832
- let selected = prepared.take(profile);
1833
- prepared.destroy_all();
1834
- selected
1835
- }
1836
-}
1837
-
1838
-#[cfg(windows)]
1839
-#[derive(Default)]
1840
-struct PreparedWinShm {
1841
- hybrid: Option<WinShmContext>,
1842
- busywait: Option<WinShmContext>,
1843
-}
1844
-
1845
-#[cfg(windows)]
1846
-impl PreparedWinShm {
1847
- fn insert(&mut self, profile: u32, ctx: WinShmContext) {
1848
- if profile == WIN_SHM_PROFILE_HYBRID {
1849
- self.hybrid = Some(ctx);
1850
- } else if profile == WIN_SHM_PROFILE_BUSYWAIT {
1851
- self.busywait = Some(ctx);
1852
- }
1853
- }
1854
-
1855
- fn take(&mut self, profile: u32) -> Option<WinShmContext> {
1856
- if profile == WIN_SHM_PROFILE_HYBRID {
1857
- self.hybrid.take()
1858
- } else if profile == WIN_SHM_PROFILE_BUSYWAIT {
1859
- self.busywait.take()
1860
- } else {
1861
- None
1862
- }
1863
- }
1864
-
1865
- fn destroy_all(&mut self) {
1866
- if let Some(mut ctx) = self.hybrid.take() {
1867
- ctx.destroy();
1868
- }
1869
- if let Some(mut ctx) = self.busywait.take() {
1870
- ctx.destroy();
1871
- }
1872
- }
1873
-
1874
- fn is_empty(&self) -> bool {
1875
- self.hybrid.is_none() && self.busywait.is_none()
1876
- }
1877
-}
1878
-
1879
-/// Windows: handle one client session over Named Pipe + optional Win SHM.
1880
-/// Standalone function for use in per-session threads.
1881
-#[cfg(windows)]
1882
-fn handle_session_win_threaded(
1883
- mut session: NpSession,
1884
- mut shm: Option<WinShmContext>,
1885
- expected_method_code: u16,
1886
- handler: Option<DispatchHandler>,
1887
- running: Arc<AtomicBool>,
1888
- learned_request_payload_bytes: Arc<AtomicU32>,
1889
- learned_response_payload_bytes: Arc<AtomicU32>,
1890
-) {
1891
- let mut recv_buf = vec![0u8; HEADER_SIZE + session.max_request_payload_bytes as usize];
1892
- let mut resp_buf = vec![0u8; session.max_response_payload_bytes as usize];
1893
- let mut item_resp_buf = vec![0u8; session.max_response_payload_bytes as usize];
1894
- let mut msg_buf = vec![0u8; HEADER_SIZE + session.max_response_payload_bytes as usize];
1895
-
1896
- while running.load(Ordering::Acquire) {
1897
- let (hdr, payload) = {
1898
- if let Some(ref mut shm_ctx) = shm {
1899
- match shm_ctx.receive(&mut recv_buf, SERVER_POLL_TIMEOUT_MS) {
1900
- Ok(mlen) => {
1901
- if mlen < HEADER_SIZE {
1902
- break;
1903
- }
1904
- let hdr = match Header::decode(&recv_buf[..mlen]) {
1905
- Ok(h) => h,
1906
- Err(_) => break,
1907
- };
1908
- let payload = &recv_buf[HEADER_SIZE..mlen];
1909
- (hdr, payload)
1910
- }
1911
- Err(crate::transport::win_shm::WinShmError::Timeout) => continue,
1912
- Err(_) => break,
1913
- }
1914
- } else {
1915
- // Named Pipe path
1916
- match session.wait_readable(SERVER_POLL_TIMEOUT_MS) {
1917
- Ok(true) => {}
1918
- Ok(false) => continue,
1919
- Err(_) => break,
1920
- }
1921
- match session.receive(&mut recv_buf) {
1922
- Ok((hdr, payload)) => (hdr, payload),
1923
- Err(_) => break,
1924
- }
1925
- }
1926
- };
1927
-
1928
- // Protocol violation: unexpected message kind terminates session
1929
- if hdr.kind != KIND_REQUEST {
1930
- break;
1931
- }
1932
-
1933
- if payload.len() <= u32::MAX as usize {
1934
- server_note_payload_capacity(&learned_request_payload_bytes, payload.len() as u32);
1935
- }
1936
-
1937
- if !method_supported_internal(expected_method_code, handler.as_ref(), hdr.code) {
1938
- let mut resp_hdr = Header {
1939
- kind: KIND_RESPONSE,
1940
- code: hdr.code,
1941
- message_id: hdr.message_id,
1942
- transport_status: protocol::STATUS_UNSUPPORTED,
1943
- item_count: 1,
1944
- ..Header::default()
1945
- };
1946
-
1947
- if let Some(ref mut shm_ctx) = shm {
1948
- let msg = ensure_client_scratch(&mut msg_buf, HEADER_SIZE);
1949
- resp_hdr.magic = MAGIC_MSG;
1950
- resp_hdr.version = VERSION;
1951
- resp_hdr.header_len = protocol::HEADER_LEN;
1952
- resp_hdr.payload_len = 0;
1953
- resp_hdr.encode(&mut msg[..HEADER_SIZE]);
1954
- if shm_ctx.send(&msg[..HEADER_SIZE]).is_err() {
1955
- break;
1956
- }
1957
- } else if session.send(&mut resp_hdr, &[]).is_err() {
1958
- break;
1959
- }
1960
- continue;
1961
- }
1962
-
1963
- // Dispatch: single-item or batch
1964
- let is_batch = (hdr.flags & FLAG_BATCH) != 0 && hdr.item_count >= 1;
1965
- let response_len;
1966
- let dispatch_result = if !is_batch {
1967
- dispatch_single_internal(
1968
- expected_method_code,
1969
- handler.as_ref(),
1970
- hdr.code,
1971
- payload,
1972
- &mut resp_buf,
1973
- )
1974
- } else {
1975
- let mut bb = BatchBuilder::new(&mut resp_buf, hdr.item_count);
1976
- let mut batch_result = Ok(0usize);
1977
-
1978
- for i in 0..hdr.item_count {
1979
- let (item_data, _item_len) = match batch_item_get(payload, hdr.item_count, i) {
1980
- Ok(v) => v,
1981
- Err(_) => {
1982
- batch_result = Err(DispatchError::BadEnvelope);
1983
- break;
1984
- }
1985
- };
1986
- let item_len = match dispatch_single_internal(
1987
- expected_method_code,
1988
- handler.as_ref(),
1989
- hdr.code,
1990
- item_data,
1991
- &mut item_resp_buf,
1992
- ) {
1993
- Ok(n) => n,
1994
- Err(err) => {
1995
- batch_result = Err(err);
1996
- break;
1997
- }
1998
- };
1999
- if bb.add(&item_resp_buf[..item_len]).is_err() {
2000
- batch_result = Err(DispatchError::Overflow);
2001
- break;
2002
- }
2003
- }
2004
- if batch_result.is_ok() {
2005
- let (n, _) = bb.finish();
2006
- batch_result = Ok(n);
2007
- }
2008
- batch_result
2009
- };
2010
-
2011
- let mut resp_hdr = Header {
2012
- kind: KIND_RESPONSE,
2013
- code: hdr.code,
2014
- message_id: hdr.message_id,
2015
- ..Header::default()
2016
- };
2017
-
2018
- match dispatch_result {
2019
- Ok(n) => {
2020
- response_len = n;
2021
- if response_len <= u32::MAX as usize {
2022
- server_note_payload_capacity(
2023
- &learned_response_payload_bytes,
2024
- response_len as u32,
2025
- );
2026
- }
2027
- resp_hdr.transport_status = STATUS_OK;
2028
- if is_batch {
2029
- resp_hdr.flags = FLAG_BATCH;
2030
- resp_hdr.item_count = hdr.item_count;
2031
- } else {
2032
- resp_hdr.flags = 0;
2033
- resp_hdr.item_count = 1;
2034
- }
2035
- }
2036
- Err(DispatchError::Overflow) => {
2037
- let current = session.max_response_payload_bytes;
2038
- if current >= u32::MAX / 2 {
2039
- server_note_payload_capacity(&learned_response_payload_bytes, u32::MAX);
2040
- } else {
2041
- server_note_payload_capacity(&learned_response_payload_bytes, current * 2);
2042
- }
2043
- resp_hdr.transport_status = STATUS_LIMIT_EXCEEDED;
2044
- resp_hdr.item_count = 1;
2045
- resp_hdr.flags = 0;
2046
- response_len = 0;
2047
- }
2048
- Err(DispatchError::BadEnvelope) => {
2049
- resp_hdr.transport_status = STATUS_BAD_ENVELOPE;
2050
- resp_hdr.item_count = 1;
2051
- resp_hdr.flags = 0;
2052
- response_len = 0;
2053
- }
2054
- Err(DispatchError::HandlerFailed) => {
2055
- resp_hdr.transport_status = STATUS_INTERNAL_ERROR;
2056
- resp_hdr.item_count = 1;
2057
- resp_hdr.flags = 0;
2058
- response_len = 0;
2059
- }
2060
- }
2061
-
2062
- if let Some(ref mut shm_ctx) = shm {
2063
- let msg_len = HEADER_SIZE + response_len;
2064
- let msg = ensure_client_scratch(&mut msg_buf, msg_len);
2065
-
2066
- resp_hdr.magic = MAGIC_MSG;
2067
- resp_hdr.version = VERSION;
2068
- resp_hdr.header_len = protocol::HEADER_LEN;
2069
- resp_hdr.payload_len = response_len as u32;
2070
-
2071
- resp_hdr.encode(&mut msg[..HEADER_SIZE]);
2072
- if response_len > 0 {
2073
- msg[HEADER_SIZE..].copy_from_slice(&resp_buf[..response_len]);
2074
- }
2075
-
2076
- if shm_ctx.send(msg).is_err() {
2077
- break;
2078
- }
2079
- if resp_hdr.transport_status == STATUS_LIMIT_EXCEEDED {
2080
- break;
2081
- }
2082
- continue;
2083
- }
2084
-
2085
- if session
2086
- .send(&mut resp_hdr, &resp_buf[..response_len])
2087
- .is_err()
2088
- {
2089
- break;
2090
- }
2091
- if resp_hdr.transport_status == STATUS_LIMIT_EXCEEDED {
2092
- break;
2093
- }
2094
- }
2095
-
2096
- if let Some(mut shm_ctx) = shm {
2097
- shm_ctx.destroy();
2098
- }
2099
- session.close();
2100
-}
2101
-
2102
-/// POSIX: Handle one client session in its own thread.
2103
-#[cfg(unix)]
2104
-fn handle_session_threaded(
2105
- mut session: UdsSession,
2106
- #[cfg(target_os = "linux")] mut shm: Option<ShmContext>,
2107
- #[cfg(not(target_os = "linux"))] _shm: Option<()>,
2108
- expected_method_code: u16,
2109
- handler: Option<DispatchHandler>,
2110
- running: Arc<AtomicBool>,
2111
- learned_request_payload_bytes: Arc<AtomicU32>,
2112
- learned_response_payload_bytes: Arc<AtomicU32>,
2113
-) {
2114
- let mut recv_buf = vec![0u8; HEADER_SIZE + session.max_request_payload_bytes as usize];
2115
- let mut resp_buf = vec![0u8; session.max_response_payload_bytes as usize];
2116
- let mut item_resp_buf = vec![0u8; session.max_response_payload_bytes as usize];
2117
- let mut msg_buf = vec![0u8; HEADER_SIZE + session.max_response_payload_bytes as usize];
2118
-
2119
- while running.load(Ordering::Acquire) {
2120
- // Receive request via the active transport
2121
- let (hdr, payload) = {
2122
- #[cfg(target_os = "linux")]
2123
- {
2124
- if let Some(ref mut shm_ctx) = shm {
2125
- match shm_ctx.receive(&mut recv_buf, SERVER_POLL_TIMEOUT_MS) {
2126
- Ok(mlen) => {
2127
- if mlen < HEADER_SIZE {
2128
- break;
2129
- }
2130
- let hdr = match Header::decode(&recv_buf[..mlen]) {
2131
- Ok(h) => h,
2132
- Err(_) => break,
2133
- };
2134
- let payload = &recv_buf[HEADER_SIZE..mlen];
2135
- (hdr, payload)
2136
- }
2137
- Err(crate::transport::shm::ShmError::Timeout) => continue,
2138
- Err(_) => break,
2139
- }
2140
- } else {
2141
- // UDS path with poll
2142
- let ready = poll_fd(session.fd(), SERVER_POLL_TIMEOUT_MS as i32);
2143
- if ready < 0 {
2144
- break;
2145
- }
2146
- if ready == 0 {
2147
- continue;
2148
- }
2149
-
2150
- match session.receive(&mut recv_buf) {
2151
- Ok((hdr, payload)) => (hdr, payload),
2152
- Err(_) => break,
2153
- }
2154
- }
2155
- }
2156
-
2157
- #[cfg(not(target_os = "linux"))]
2158
- {
2159
- let ready = poll_fd(session.fd(), SERVER_POLL_TIMEOUT_MS as i32);
2160
- if ready < 0 {
2161
- break;
2162
- }
2163
- if ready == 0 {
2164
- continue;
2165
- }
2166
-
2167
- match session.receive(&mut recv_buf) {
2168
- Ok((hdr, payload)) => (hdr, payload),
2169
- Err(_) => break,
2170
- }
2171
- }
2172
- };
2173
-
2174
- // Protocol violation: unexpected message kind terminates session
2175
- if hdr.kind != KIND_REQUEST {
2176
- break;
2177
- }
2178
-
2179
- if payload.len() <= u32::MAX as usize {
2180
- server_note_payload_capacity(&learned_request_payload_bytes, payload.len() as u32);
2181
- }
2182
-
2183
- if !method_supported_internal(expected_method_code, handler.as_ref(), hdr.code) {
2184
- let mut resp_hdr = Header {
2185
- kind: KIND_RESPONSE,
2186
- code: hdr.code,
2187
- message_id: hdr.message_id,
2188
- transport_status: protocol::STATUS_UNSUPPORTED,
2189
- item_count: 1,
2190
- ..Header::default()
2191
- };
2192
-
2193
- #[cfg(target_os = "linux")]
2194
- {
2195
- if let Some(ref mut shm_ctx) = shm {
2196
- let msg = ensure_client_scratch(&mut msg_buf, HEADER_SIZE);
2197
- resp_hdr.magic = MAGIC_MSG;
2198
- resp_hdr.version = VERSION;
2199
- resp_hdr.header_len = protocol::HEADER_LEN;
2200
- resp_hdr.payload_len = 0;
2201
- resp_hdr.encode(&mut msg[..HEADER_SIZE]);
2202
- if shm_ctx.send(&msg[..HEADER_SIZE]).is_err() {
2203
- break;
2204
- }
2205
- continue;
2206
- }
2207
- }
2208
-
2209
- if session.send(&mut resp_hdr, &[]).is_err() {
2210
- break;
2211
- }
2212
- continue;
2213
- }
2214
-
2215
- // Dispatch: single-item or batch
2216
- let is_batch = (hdr.flags & FLAG_BATCH) != 0 && hdr.item_count >= 1;
2217
- let response_len;
2218
- let dispatch_result = if !is_batch {
2219
- dispatch_single_internal(
2220
- expected_method_code,
2221
- handler.as_ref(),
2222
- hdr.code,
2223
- payload,
2224
- &mut resp_buf,
2225
- )
2226
- } else {
2227
- let mut bb = BatchBuilder::new(&mut resp_buf, hdr.item_count);
2228
- let mut batch_result = Ok(0usize);
2229
-
2230
- for i in 0..hdr.item_count {
2231
- let (item_data, _item_len) = match batch_item_get(payload, hdr.item_count, i) {
2232
- Ok(v) => v,
2233
- Err(_) => {
2234
- batch_result = Err(DispatchError::BadEnvelope);
2235
- break;
2236
- }
2237
- };
2238
- let item_len = match dispatch_single_internal(
2239
- expected_method_code,
2240
- handler.as_ref(),
2241
- hdr.code,
2242
- item_data,
2243
- &mut item_resp_buf,
2244
- ) {
2245
- Ok(n) => n,
2246
- Err(err) => {
2247
- batch_result = Err(err);
2248
- break;
2249
- }
2250
- };
2251
- if bb.add(&item_resp_buf[..item_len]).is_err() {
2252
- batch_result = Err(DispatchError::Overflow);
2253
- break;
2254
- }
2255
- }
2256
- if batch_result.is_ok() {
2257
- let (n, _) = bb.finish();
2258
- batch_result = Ok(n);
2259
- }
2260
- batch_result
2261
- };
2262
-
2263
- // Build response header
2264
- let mut resp_hdr = Header {
2265
- kind: KIND_RESPONSE,
2266
- code: hdr.code,
2267
- message_id: hdr.message_id,
2268
- ..Header::default()
2269
- };
2270
-
2271
- match dispatch_result {
2272
- Ok(n) => {
2273
- response_len = n;
2274
- if response_len <= u32::MAX as usize {
2275
- server_note_payload_capacity(
2276
- &learned_response_payload_bytes,
2277
- response_len as u32,
2278
- );
2279
- }
2280
- resp_hdr.transport_status = STATUS_OK;
2281
- if is_batch {
2282
- resp_hdr.flags = FLAG_BATCH;
2283
- resp_hdr.item_count = hdr.item_count;
2284
- } else {
2285
- resp_hdr.flags = 0;
2286
- resp_hdr.item_count = 1;
2287
- }
2288
- }
2289
- Err(DispatchError::Overflow) => {
2290
- let current = session.max_response_payload_bytes;
2291
- if current >= u32::MAX / 2 {
2292
- server_note_payload_capacity(&learned_response_payload_bytes, u32::MAX);
2293
- } else {
2294
- server_note_payload_capacity(&learned_response_payload_bytes, current * 2);
2295
- }
2296
- resp_hdr.transport_status = STATUS_LIMIT_EXCEEDED;
2297
- resp_hdr.item_count = 1;
2298
- resp_hdr.flags = 0;
2299
- response_len = 0;
2300
- }
2301
- Err(DispatchError::BadEnvelope) => {
2302
- resp_hdr.transport_status = STATUS_BAD_ENVELOPE;
2303
- resp_hdr.item_count = 1;
2304
- resp_hdr.flags = 0;
2305
- response_len = 0;
2306
- }
2307
- Err(DispatchError::HandlerFailed) => {
2308
- resp_hdr.transport_status = STATUS_INTERNAL_ERROR;
2309
- resp_hdr.item_count = 1;
2310
- resp_hdr.flags = 0;
2311
- response_len = 0;
2312
- }
2313
- }
2314
-
2315
- // Send response via the active transport
2316
- #[cfg(target_os = "linux")]
2317
- {
2318
- if let Some(ref mut shm_ctx) = shm {
2319
- let msg_len = HEADER_SIZE + response_len;
2320
- let msg = ensure_client_scratch(&mut msg_buf, msg_len);
2321
-
2322
- resp_hdr.magic = MAGIC_MSG;
2323
- resp_hdr.version = VERSION;
2324
- resp_hdr.header_len = protocol::HEADER_LEN;
2325
- resp_hdr.payload_len = response_len as u32;
2326
-
2327
- resp_hdr.encode(&mut msg[..HEADER_SIZE]);
2328
- if response_len > 0 {
2329
- msg[HEADER_SIZE..].copy_from_slice(&resp_buf[..response_len]);
2330
- }
2331
-
2332
- if shm_ctx.send(msg).is_err() {
2333
- break;
2334
- }
2335
- if resp_hdr.transport_status == STATUS_LIMIT_EXCEEDED {
2336
- break;
2337
- }
2338
- continue;
2339
- }
2340
- }
2341
-
2342
- // UDS path
2343
- if session
2344
- .send(&mut resp_hdr, &resp_buf[..response_len])
2345
- .is_err()
2346
- {
2347
- break;
2348
- }
2349
- if resp_hdr.transport_status == STATUS_LIMIT_EXCEEDED {
2350
- break;
2351
- }
2352
- }
2353
-
2354
- // Cleanup
2355
- #[cfg(target_os = "linux")]
2356
- {
2357
- if let Some(mut shm_ctx) = shm {
2358
- shm_ctx.destroy();
2359
- }
2360
- }
2361
- drop(session);
2362
-}
2363
-
2364
-// ---------------------------------------------------------------------------
2365
-// Internal: poll helper
2366
-// ---------------------------------------------------------------------------
2367
-
2368
-/// Poll a file descriptor for readability with a timeout in milliseconds.
2369
-/// Returns: 1 = data ready, 0 = timeout, -1 = error/hangup.
2370
-#[cfg(unix)]
2371
-fn poll_fd(fd: i32, timeout_ms: i32) -> i32 {
2372
- let mut pfd = libc::pollfd {
2373
- fd,
2374
- events: libc::POLLIN,
2375
- revents: 0,
2376
- };
2377
-
2378
- let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
2379
-
2380
- if ret < 0 {
2381
- let errno = unsafe { *libc::__errno_location() };
2382
- if errno == libc::EINTR {
2383
- return 0;
2384
- }
2385
- return -1;
2386
- }
2387
-
2388
- if ret == 0 {
2389
- return 0;
2390
- }
2391
-
2392
- if pfd.revents & (libc::POLLERR | libc::POLLHUP | libc::POLLNVAL) != 0 {
2393
- return -1;
2394
- }
2395
-
2396
- if pfd.revents & libc::POLLIN != 0 {
2397
- return 1;
2398
- }
2399
-
2400
- 0
2401
-}
2402
-
2403
-// ---------------------------------------------------------------------------
2404
-// L3: Client-side cgroups snapshot cache
2405
-// ---------------------------------------------------------------------------
2406
-
2407
-/// Cached copy of a single cgroup item. Owns its strings.
2408
-/// Built from ephemeral L2 views during cache construction.
2409
-#[derive(Debug, Clone)]
2410
-pub struct CgroupsCacheItem {
2411
- pub hash: u32,
2412
- pub options: u32,
2413
- pub enabled: u32,
2414
- pub name: String,
2415
- pub path: String,
2416
-}
2417
-
2418
-/// L3 cache status snapshot (for diagnostics, not hot path).
2419
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2420
-pub struct CgroupsCacheStatus {
2421
- pub populated: bool,
2422
- pub item_count: u32,
2423
- pub systemd_enabled: u32,
2424
- pub generation: u64,
2425
- pub refresh_success_count: u32,
2426
- pub refresh_failure_count: u32,
2427
- pub connection_state: ClientState,
2428
- /// Monotonic milliseconds of last successful refresh (0 if never).
2429
- pub last_refresh_ts: u64,
2430
-}
2431
-
2432
-/// Default response buffer size for L3 cache refresh.
2433
-const CACHE_RESPONSE_BUF_SIZE: usize = 65536;
2434
-
2435
-#[derive(Debug, Clone, Copy, Default)]
2436
-struct CgroupsHashBucket {
2437
- index: u32,
2438
- used: bool,
2439
-}
2440
-
2441
-fn cache_hash_name(name: &str) -> u32 {
2442
- let mut h: u32 = 5381;
2443
- for b in name.as_bytes() {
2444
- h = ((h << 5).wrapping_add(h)).wrapping_add(*b as u32);
2445
- }
2446
- h
2447
-}
2448
-
2449
-/// L3 client-side cgroups snapshot cache.
2450
-///
2451
-/// Wraps an L2 client and maintains a local owned copy of the most
2452
-/// recent successful snapshot. Lookup by hash+name is O(1) via HashMap.
2453
-///
2454
-/// On refresh failure, the previous cache is preserved. The cache
2455
-/// is empty only if no successful refresh has ever occurred.
2456
-pub struct CgroupsCache {
2457
- client: RawClient,
2458
- items: Vec<CgroupsCacheItem>,
2459
- /// Open-addressing hash table: (hash ^ djb2(name)) -> index into items vec
2460
- buckets: Vec<CgroupsHashBucket>,
2461
- systemd_enabled: u32,
2462
- generation: u64,
2463
- populated: bool,
2464
- refresh_success_count: u32,
2465
- refresh_failure_count: u32,
2466
- /// Monotonic reference point for timestamp calculation
2467
- epoch: std::time::Instant,
2468
- /// Monotonic ms of last successful refresh (0 if never)
2469
- pub last_refresh_ts: u64,
2470
-}
2471
-
2472
-impl CgroupsCache {
2473
- /// Create a new L3 cache. Creates the underlying L2 client context.
2474
- /// Does NOT connect. Does NOT require the server to be running.
2475
- /// Cache starts empty (populated == false).
2476
- pub fn new(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
2477
- CgroupsCache {
2478
- client: RawClient::new_snapshot(run_dir, service_name, config),
2479
- items: Vec::new(),
2480
- buckets: Vec::new(),
2481
- systemd_enabled: 0,
2482
- generation: 0,
2483
- populated: false,
2484
- refresh_success_count: 0,
2485
- refresh_failure_count: 0,
2486
- epoch: std::time::Instant::now(),
2487
- last_refresh_ts: 0,
2488
- }
2489
- }
2490
-
2491
- /// Refresh the cache. Drives the L2 client (connect/reconnect as
2492
- /// needed) and requests a fresh snapshot. On success, rebuilds the
2493
- /// local cache. On failure, preserves the previous cache.
2494
- ///
2495
- /// Returns true if the cache was updated.
2496
- pub fn refresh(&mut self) -> bool {
2497
- // Drive L2 connection lifecycle
2498
- self.client.refresh();
2499
-
2500
- // Attempt snapshot call
2501
- match self.client.call_snapshot() {
2502
- Ok(view) => {
2503
- // Build new cache from snapshot view
2504
- let mut new_items = Vec::with_capacity(view.item_count as usize);
2505
- for i in 0..view.item_count {
2506
- match view.item(i) {
2507
- Ok(iv) => {
2508
- let name = match iv.name.as_str() {
2509
- Ok(s) => s.to_string(),
2510
- Err(_) => {
2511
- // Non-UTF8 name: use lossy conversion
2512
- String::from_utf8_lossy(iv.name.as_bytes()).into_owned()
2513
- }
2514
- };
2515
- let path = match iv.path.as_str() {
2516
- Ok(s) => s.to_string(),
2517
- Err(_) => String::from_utf8_lossy(iv.path.as_bytes()).into_owned(),
2518
- };
2519
- new_items.push(CgroupsCacheItem {
2520
- hash: iv.hash,
2521
- options: iv.options,
2522
- enabled: iv.enabled,
2523
- name,
2524
- path,
2525
- });
2526
- }
2527
- Err(_) => {
2528
- // Malformed item: abort, preserve old cache
2529
- self.refresh_failure_count += 1;
2530
- return false;
2531
- }
2532
- }
2533
- }
2534
-
2535
- // Rebuild open-addressing lookup table.
2536
- let mut buckets = Vec::new();
2537
- if !new_items.is_empty() {
2538
- let bcount = next_power_of_2_u32((new_items.len() as u32) * 2) as usize;
2539
- buckets.resize(bcount, CgroupsHashBucket::default());
2540
- let mask = (bcount - 1) as u32;
2541
- for (i, item) in new_items.iter().enumerate() {
2542
- let mut slot = (item.hash ^ cache_hash_name(&item.name)) & mask;
2543
- while buckets[slot as usize].used {
2544
- slot = (slot + 1) & mask;
2545
- }
2546
- buckets[slot as usize] = CgroupsHashBucket {
2547
- index: i as u32,
2548
- used: true,
2549
- };
2550
- }
2551
- }
2552
-
2553
- // Replace old cache
2554
- self.items = new_items;
2555
- self.buckets = buckets;
2556
- self.systemd_enabled = view.systemd_enabled;
2557
- self.generation = view.generation;
2558
- self.populated = true;
2559
- self.refresh_success_count += 1;
2560
- self.last_refresh_ts = self.epoch.elapsed().as_millis() as u64;
2561
- true
2562
- }
2563
- Err(_) => {
2564
- // Refresh failed: preserve previous cache
2565
- self.refresh_failure_count += 1;
2566
- false
2567
- }
2568
- }
2569
- }
2570
-
2571
- /// Returns true if at least one successful refresh has occurred.
2572
- /// Cheap cached boolean. No I/O, no syscalls.
2573
- ///
2574
- /// Note: ready means "has cached data", not "is connected."
2575
- #[inline]
2576
- pub fn ready(&self) -> bool {
2577
- self.populated
2578
- }
2579
-
2580
- /// Look up a cached item by hash + name. O(1) via open-addressing hash
2581
- /// table. No I/O.
2582
- pub fn lookup(&self, hash: u32, name: &str) -> Option<&CgroupsCacheItem> {
2583
- if !self.populated {
2584
- return None;
2585
- }
2586
- if !self.buckets.is_empty() {
2587
- let mask = (self.buckets.len() - 1) as u32;
2588
- let mut slot = (hash ^ cache_hash_name(name)) & mask;
2589
- while self.buckets[slot as usize].used {
2590
- let item = &self.items[self.buckets[slot as usize].index as usize];
2591
- if item.hash == hash && item.name == name {
2592
- return Some(item);
2593
- }
2594
- slot = (slot + 1) & mask;
2595
- }
2596
- return None;
2597
- }
2598
-
2599
- self.items
2600
- .iter()
2601
- .find(|item| item.hash == hash && item.name == name)
2602
- }
2603
-
2604
- /// Fill a status snapshot for diagnostics.
2605
- pub fn status(&self) -> CgroupsCacheStatus {
2606
- CgroupsCacheStatus {
2607
- populated: self.populated,
2608
- item_count: self.items.len() as u32,
2609
- systemd_enabled: self.systemd_enabled,
2610
- generation: self.generation,
2611
- refresh_success_count: self.refresh_success_count,
2612
- refresh_failure_count: self.refresh_failure_count,
2613
- connection_state: self.client.state,
2614
- last_refresh_ts: self.last_refresh_ts,
2615
- }
2616
- }
2617
-
2618
- /// Close the cache: free all cached items, close the L2 client.
2619
- pub fn close(&mut self) {
2620
- self.items.clear();
2621
- self.buckets.clear();
2622
- self.populated = false;
2623
- self.client.close();
2624
- }
2625
-}
2626
-
2627
-impl Drop for CgroupsCache {
2628
- fn drop(&mut self) {
2629
- self.close();
2630
- }
2631
-}
2632
-
2633
-// ---------------------------------------------------------------------------
2634
-// Tests
2635
-// ---------------------------------------------------------------------------
59
+#[cfg(all(test, unix))]
60
+use client::{ClientResponseRef, ClientResponseSource};
61
+#[cfg(all(test, unix))]
62
+use common::CACHE_RESPONSE_BUF_SIZE;
63
+#[cfg(all(test, unix))]
64
+use dispatch::dispatch_single;
65
+#[cfg(all(test, unix))]
66
+use server_session_unix::poll_fd;
67
68
#[cfg(all(test, unix))]
69
#[path = "raw_unix_tests.rs"]
src/crates/netipc/src/service/raw/apps_lookup.rs
new
+91
@@ -0,0 +1,91 @@
1
+use super::client::{ClientConfig, RawCallKind, RawClient};
2
+use super::dispatch::{DispatchError, DispatchHandler};
3
+use crate::protocol::{
4
+ self, AppsLookupBuilder, AppsLookupRequestView, AppsLookupResponseView, NipcError,
5
+ APPS_LOOKUP_KEY_SIZE, APPS_LOOKUP_REQ_HDR_SIZE, LOOKUP_DIR_ENTRY_SIZE, METHOD_APPS_LOOKUP,
6
+};
7
+use std::sync::Arc;
8
+
9
+pub type AppsLookupHandler =
10
+ Arc<dyn for<'a> Fn(&AppsLookupRequestView, &mut AppsLookupBuilder<'a>) -> bool + Send + Sync>;
11
+
12
+impl RawClient {
13
+ /// Create a new client context bound to the apps-lookup service kind.
14
+ /// Does NOT connect. Does NOT require the server to be running.
15
+ pub fn new_apps_lookup(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
16
+ Self::new_bound(run_dir, service_name, METHOD_APPS_LOOKUP, config)
17
+ }
18
+
19
+ /// Blocking typed call: APPS_LOOKUP method.
20
+ ///
21
+ /// The returned view is valid until the next typed call on this client.
22
+ pub fn call_apps_lookup(
23
+ &mut self,
24
+ pids: &[u32],
25
+ ) -> Result<AppsLookupResponseView<'_>, NipcError> {
26
+ self.validate_method(METHOD_APPS_LOOKUP)?;
27
+
28
+ let dir_size = pids
29
+ .len()
30
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
31
+ .ok_or(NipcError::Overflow)?;
32
+ let key_size = pids
33
+ .len()
34
+ .checked_mul(APPS_LOOKUP_KEY_SIZE)
35
+ .ok_or(NipcError::Overflow)?;
36
+ let req_size = APPS_LOOKUP_REQ_HDR_SIZE
37
+ .checked_add(dir_size)
38
+ .and_then(|v| v.checked_add(key_size))
39
+ .ok_or(NipcError::Overflow)?;
40
+ let req_len = {
41
+ let req_buf = self.request_scratch(req_size);
42
+ protocol::encode_apps_lookup_request(pids, req_buf)?
43
+ };
44
+ let response =
45
+ self.raw_call_with_retry(METHOD_APPS_LOOKUP, req_len, RawCallKind::single())?;
46
+ let view = AppsLookupResponseView::decode(self.response_payload(response)?)?;
47
+ if view.item_count != pids.len() as u32 {
48
+ return Err(NipcError::BadItemCount);
49
+ }
50
+ for (i, expected) in pids.iter().enumerate() {
51
+ let item = view.item(i as u32)?;
52
+ if item.pid != *expected {
53
+ return Err(NipcError::BadLayout);
54
+ }
55
+ }
56
+ Ok(view)
57
+ }
58
+}
59
+
60
+pub fn apps_lookup_dispatch(handler: AppsLookupHandler) -> DispatchHandler {
61
+ Arc::new(move |request, response_buf| {
62
+ let request =
63
+ AppsLookupRequestView::decode(request).map_err(|_| DispatchError::BadEnvelope)?;
64
+ let dir_size = (request.item_count as usize)
65
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
66
+ .ok_or(DispatchError::Overflow)?;
67
+ let min_required = protocol::APPS_LOOKUP_RESP_HDR_SIZE
68
+ .checked_add(dir_size)
69
+ .ok_or(DispatchError::Overflow)?;
70
+ if response_buf.len() < min_required {
71
+ return Err(DispatchError::Overflow);
72
+ }
73
+ let mut builder = AppsLookupBuilder::new(response_buf, request.item_count, 0);
74
+ if !handler(&request, &mut builder) {
75
+ return Err(DispatchError::HandlerFailed);
76
+ }
77
+ if let Some(err) = builder.error() {
78
+ return match err {
79
+ NipcError::Overflow => Err(DispatchError::Overflow),
80
+ _ => Err(DispatchError::BadEnvelope),
81
+ };
82
+ }
83
+ if builder.item_count() != request.item_count {
84
+ return Err(DispatchError::BadEnvelope);
85
+ }
86
+ builder.finish().map_err(|err| match err {
87
+ NipcError::Overflow => DispatchError::Overflow,
88
+ _ => DispatchError::BadEnvelope,
89
+ })
90
+ })
91
+}
src/crates/netipc/src/service/raw/cgroups_cache.rs
new
+215
@@ -0,0 +1,215 @@
1
+use super::client::{ClientConfig, ClientState, RawClient};
2
+use super::common::next_power_of_2_u32;
3
+
4
+/// Cached copy of a single cgroup item. Owns its strings.
5
+/// Built from ephemeral L2 views during cache construction.
6
+#[derive(Debug, Clone)]
7
+pub struct CgroupsCacheItem {
8
+ pub hash: u32,
9
+ pub options: u32,
10
+ pub enabled: u32,
11
+ pub name: String,
12
+ pub path: String,
13
+}
14
+
15
+/// L3 cache status snapshot (for diagnostics, not hot path).
16
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17
+pub struct CgroupsCacheStatus {
18
+ pub populated: bool,
19
+ pub item_count: u32,
20
+ pub systemd_enabled: u32,
21
+ pub generation: u64,
22
+ pub refresh_success_count: u32,
23
+ pub refresh_failure_count: u32,
24
+ pub connection_state: ClientState,
25
+ /// Monotonic milliseconds of last successful refresh (0 if never).
26
+ pub last_refresh_ts: u64,
27
+}
28
+
29
+#[derive(Debug, Clone, Copy, Default)]
30
+struct CgroupsHashBucket {
31
+ index: u32,
32
+ used: bool,
33
+}
34
+
35
+fn cache_hash_name(name: &str) -> u32 {
36
+ let mut h: u32 = 5381;
37
+ for b in name.as_bytes() {
38
+ h = ((h << 5).wrapping_add(h)).wrapping_add(*b as u32);
39
+ }
40
+ h
41
+}
42
+
43
+/// L3 client-side cgroups snapshot cache.
44
+///
45
+/// Wraps an L2 client and maintains a local owned copy of the most
46
+/// recent successful snapshot. Lookup by hash+name is O(1) via HashMap.
47
+///
48
+/// On refresh failure, the previous cache is preserved. The cache
49
+/// is empty only if no successful refresh has ever occurred.
50
+pub struct CgroupsCache {
51
+ pub(super) client: RawClient,
52
+ items: Vec<CgroupsCacheItem>,
53
+ /// Open-addressing hash table: (hash ^ djb2(name)) -> index into items vec
54
+ buckets: Vec<CgroupsHashBucket>,
55
+ systemd_enabled: u32,
56
+ generation: u64,
57
+ populated: bool,
58
+ refresh_success_count: u32,
59
+ refresh_failure_count: u32,
60
+ /// Monotonic reference point for timestamp calculation
61
+ epoch: std::time::Instant,
62
+ /// Monotonic ms of last successful refresh (0 if never)
63
+ pub last_refresh_ts: u64,
64
+}
65
+
66
+impl CgroupsCache {
67
+ /// Create a new L3 cache. Creates the underlying L2 client context.
68
+ /// Does NOT connect. Does NOT require the server to be running.
69
+ /// Cache starts empty (populated == false).
70
+ pub fn new(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
71
+ CgroupsCache {
72
+ client: RawClient::new_snapshot(run_dir, service_name, config),
73
+ items: Vec::new(),
74
+ buckets: Vec::new(),
75
+ systemd_enabled: 0,
76
+ generation: 0,
77
+ populated: false,
78
+ refresh_success_count: 0,
79
+ refresh_failure_count: 0,
80
+ epoch: std::time::Instant::now(),
81
+ last_refresh_ts: 0,
82
+ }
83
+ }
84
+
85
+ /// Refresh the cache. Drives the L2 client (connect/reconnect as
86
+ /// needed) and requests a fresh snapshot. On success, rebuilds the
87
+ /// local cache. On failure, preserves the previous cache.
88
+ ///
89
+ /// Returns true if the cache was updated.
90
+ pub fn refresh(&mut self) -> bool {
91
+ self.client.refresh();
92
+
93
+ match self.client.call_snapshot() {
94
+ Ok(view) => {
95
+ let mut new_items = Vec::with_capacity(view.item_count as usize);
96
+ for i in 0..view.item_count {
97
+ match view.item(i) {
98
+ Ok(iv) => {
99
+ let name = match iv.name.as_str() {
100
+ Ok(s) => s.to_string(),
101
+ Err(_) => String::from_utf8_lossy(iv.name.as_bytes()).into_owned(),
102
+ };
103
+ let path = match iv.path.as_str() {
104
+ Ok(s) => s.to_string(),
105
+ Err(_) => String::from_utf8_lossy(iv.path.as_bytes()).into_owned(),
106
+ };
107
+ new_items.push(CgroupsCacheItem {
108
+ hash: iv.hash,
109
+ options: iv.options,
110
+ enabled: iv.enabled,
111
+ name,
112
+ path,
113
+ });
114
+ }
115
+ Err(_) => {
116
+ self.refresh_failure_count += 1;
117
+ return false;
118
+ }
119
+ }
120
+ }
121
+
122
+ let mut buckets = Vec::new();
123
+ if !new_items.is_empty() {
124
+ let bcount = next_power_of_2_u32((new_items.len() as u32) * 2) as usize;
125
+ buckets.resize(bcount, CgroupsHashBucket::default());
126
+ let mask = (bcount - 1) as u32;
127
+ for (i, item) in new_items.iter().enumerate() {
128
+ let mut slot = (item.hash ^ cache_hash_name(&item.name)) & mask;
129
+ while buckets[slot as usize].used {
130
+ slot = (slot + 1) & mask;
131
+ }
132
+ buckets[slot as usize] = CgroupsHashBucket {
133
+ index: i as u32,
134
+ used: true,
135
+ };
136
+ }
137
+ }
138
+
139
+ self.items = new_items;
140
+ self.buckets = buckets;
141
+ self.systemd_enabled = view.systemd_enabled;
142
+ self.generation = view.generation;
143
+ self.populated = true;
144
+ self.refresh_success_count += 1;
145
+ self.last_refresh_ts = self.epoch.elapsed().as_millis() as u64;
146
+ true
147
+ }
148
+ Err(_) => {
149
+ self.refresh_failure_count += 1;
150
+ false
151
+ }
152
+ }
153
+ }
154
+
155
+ /// Returns true if at least one successful refresh has occurred.
156
+ /// Cheap cached boolean. No I/O, no syscalls.
157
+ ///
158
+ /// Note: ready means "has cached data", not "is connected."
159
+ #[inline]
160
+ pub fn ready(&self) -> bool {
161
+ self.populated
162
+ }
163
+
164
+ /// Look up a cached item by hash + name. O(1) via open-addressing hash
165
+ /// table. No I/O.
166
+ pub fn lookup(&self, hash: u32, name: &str) -> Option<&CgroupsCacheItem> {
167
+ if !self.populated {
168
+ return None;
169
+ }
170
+ if !self.buckets.is_empty() {
171
+ let mask = (self.buckets.len() - 1) as u32;
172
+ let mut slot = (hash ^ cache_hash_name(name)) & mask;
173
+ while self.buckets[slot as usize].used {
174
+ let item = &self.items[self.buckets[slot as usize].index as usize];
175
+ if item.hash == hash && item.name == name {
176
+ return Some(item);
177
+ }
178
+ slot = (slot + 1) & mask;
179
+ }
180
+ return None;
181
+ }
182
+
183
+ self.items
184
+ .iter()
185
+ .find(|item| item.hash == hash && item.name == name)
186
+ }
187
+
188
+ /// Fill a status snapshot for diagnostics.
189
+ pub fn status(&self) -> CgroupsCacheStatus {
190
+ CgroupsCacheStatus {
191
+ populated: self.populated,
192
+ item_count: self.items.len() as u32,
193
+ systemd_enabled: self.systemd_enabled,
194
+ generation: self.generation,
195
+ refresh_success_count: self.refresh_success_count,
196
+ refresh_failure_count: self.refresh_failure_count,
197
+ connection_state: self.client.status().state,
198
+ last_refresh_ts: self.last_refresh_ts,
199
+ }
200
+ }
201
+
202
+ /// Close the cache: free all cached items, close the L2 client.
203
+ pub fn close(&mut self) {
204
+ self.items.clear();
205
+ self.buckets.clear();
206
+ self.populated = false;
207
+ self.client.close();
208
+ }
209
+}
210
+
211
+impl Drop for CgroupsCache {
212
+ fn drop(&mut self) {
213
+ self.close();
214
+ }
215
+}
src/crates/netipc/src/service/raw/cgroups_lookup.rs
new
+100
@@ -0,0 +1,100 @@
1
+use super::client::{ClientConfig, RawCallKind, RawClient};
2
+use super::dispatch::{DispatchError, DispatchHandler};
3
+use crate::protocol::{
4
+ self, CgroupsLookupBuilder, CgroupsLookupRequestView, CgroupsLookupResponseView, NipcError,
5
+ CGROUPS_LOOKUP_REQ_HDR_SIZE, LOOKUP_DIR_ENTRY_SIZE, METHOD_CGROUPS_LOOKUP,
6
+};
7
+use std::sync::Arc;
8
+
9
+pub type CgroupsLookupHandler = Arc<
10
+ dyn for<'a> Fn(&CgroupsLookupRequestView, &mut CgroupsLookupBuilder<'a>) -> bool + Send + Sync,
11
+>;
12
+
13
+impl RawClient {
14
+ /// Create a new client context bound to the cgroups-lookup service kind.
15
+ /// Does NOT connect. Does NOT require the server to be running.
16
+ pub fn new_cgroups_lookup(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
17
+ Self::new_bound(run_dir, service_name, METHOD_CGROUPS_LOOKUP, config)
18
+ }
19
+
20
+ /// Blocking typed call: CGROUPS_LOOKUP method.
21
+ ///
22
+ /// The returned view is valid until the next typed call on this client.
23
+ pub fn call_cgroups_lookup(
24
+ &mut self,
25
+ paths: &[&[u8]],
26
+ ) -> Result<CgroupsLookupResponseView<'_>, NipcError> {
27
+ self.validate_method(METHOD_CGROUPS_LOOKUP)?;
28
+
29
+ let dir_size = paths
30
+ .len()
31
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
32
+ .ok_or(NipcError::Overflow)?;
33
+ let mut req_size = CGROUPS_LOOKUP_REQ_HDR_SIZE
34
+ .checked_add(dir_size)
35
+ .ok_or(NipcError::Overflow)?;
36
+ let mut data = req_size;
37
+ for path in paths {
38
+ let aligned = data
39
+ .checked_add(7)
40
+ .map(|v| v & !7)
41
+ .ok_or(NipcError::Overflow)?;
42
+ data = aligned
43
+ .checked_add(path.len())
44
+ .and_then(|v| v.checked_add(1))
45
+ .ok_or(NipcError::Overflow)?;
46
+ }
47
+ req_size = data;
48
+
49
+ let req_len = {
50
+ let req_buf = self.request_scratch(req_size);
51
+ protocol::encode_cgroups_lookup_request(paths, req_buf)?
52
+ };
53
+ let response =
54
+ self.raw_call_with_retry(METHOD_CGROUPS_LOOKUP, req_len, RawCallKind::single())?;
55
+ let view = CgroupsLookupResponseView::decode(self.response_payload(response)?)?;
56
+ if view.item_count != paths.len() as u32 {
57
+ return Err(NipcError::BadItemCount);
58
+ }
59
+ for (i, expected) in paths.iter().enumerate() {
60
+ let item = view.item(i as u32)?;
61
+ if item.path.as_bytes() != *expected {
62
+ return Err(NipcError::BadLayout);
63
+ }
64
+ }
65
+ Ok(view)
66
+ }
67
+}
68
+
69
+pub fn cgroups_lookup_dispatch(handler: CgroupsLookupHandler) -> DispatchHandler {
70
+ Arc::new(move |request, response_buf| {
71
+ let request =
72
+ CgroupsLookupRequestView::decode(request).map_err(|_| DispatchError::BadEnvelope)?;
73
+ let dir_size = (request.item_count as usize)
74
+ .checked_mul(LOOKUP_DIR_ENTRY_SIZE)
75
+ .ok_or(DispatchError::Overflow)?;
76
+ let min_required = protocol::CGROUPS_LOOKUP_RESP_HDR_SIZE
77
+ .checked_add(dir_size)
78
+ .ok_or(DispatchError::Overflow)?;
79
+ if response_buf.len() < min_required {
80
+ return Err(DispatchError::Overflow);
81
+ }
82
+ let mut builder = CgroupsLookupBuilder::new(response_buf, request.item_count, 0);
83
+ if !handler(&request, &mut builder) {
84
+ return Err(DispatchError::HandlerFailed);
85
+ }
86
+ if let Some(err) = builder.error() {
87
+ return match err {
88
+ NipcError::Overflow => Err(DispatchError::Overflow),
89
+ _ => Err(DispatchError::BadEnvelope),
90
+ };
91
+ }
92
+ if builder.item_count() != request.item_count {
93
+ return Err(DispatchError::BadEnvelope);
94
+ }
95
+ builder.finish().map_err(|err| match err {
96
+ NipcError::Overflow => DispatchError::Overflow,
97
+ _ => DispatchError::BadEnvelope,
98
+ })
99
+ })
100
+}
src/crates/netipc/src/service/raw/cgroups_snapshot.rs
new
+67
@@ -0,0 +1,67 @@
1
+use super::client::{ClientConfig, RawCallKind, RawClient};
2
+use super::dispatch::{DispatchError, DispatchHandler};
3
+use crate::protocol::{
4
+ self, CgroupsRequest, CgroupsResponseView, NipcError, METHOD_CGROUPS_SNAPSHOT,
5
+};
6
+use std::sync::Arc;
7
+
8
+pub type SnapshotHandler =
9
+ Arc<dyn for<'a> Fn(&CgroupsRequest, &mut protocol::CgroupsBuilder<'a>) -> bool + Send + Sync>;
10
+
11
+impl RawClient {
12
+ /// Create a new client context bound to the cgroups-snapshot service kind.
13
+ /// Does NOT connect. Does NOT require the server to be running.
14
+ pub fn new_snapshot(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
15
+ Self::new_bound(run_dir, service_name, METHOD_CGROUPS_SNAPSHOT, config)
16
+ }
17
+
18
+ /// Blocking typed call: encode request, send, receive, check
19
+ /// transport_status, decode response.
20
+ ///
21
+ /// The returned view is valid until the next typed call on this client.
22
+ pub fn call_snapshot(&mut self) -> Result<CgroupsResponseView<'_>, NipcError> {
23
+ self.validate_method(METHOD_CGROUPS_SNAPSHOT)?;
24
+ let req = CgroupsRequest {
25
+ layout_version: 1,
26
+ flags: 0,
27
+ };
28
+ let req_len = {
29
+ let req_buf = self.request_scratch(4);
30
+ let req_len = req.encode(req_buf);
31
+ if req_len == 0 {
32
+ return Err(NipcError::Truncated);
33
+ }
34
+ req_len
35
+ };
36
+
37
+ let response =
38
+ self.raw_call_with_retry(METHOD_CGROUPS_SNAPSHOT, req_len, RawCallKind::single())?;
39
+ CgroupsResponseView::decode(self.response_payload(response)?)
40
+ }
41
+}
42
+
43
+pub fn snapshot_max_items(response_buf_size: usize, override_max_items: u32) -> u32 {
44
+ if override_max_items != 0 {
45
+ return override_max_items;
46
+ }
47
+ protocol::estimate_cgroups_max_items(response_buf_size)
48
+}
49
+
50
+pub fn snapshot_dispatch(handler: SnapshotHandler, max_items: u32) -> DispatchHandler {
51
+ Arc::new(move |request, response_buf| {
52
+ let request = CgroupsRequest::decode(request).map_err(|_| DispatchError::BadEnvelope)?;
53
+ let item_budget = snapshot_max_items(response_buf.len(), max_items);
54
+ if item_budget == 0 {
55
+ return Err(DispatchError::Overflow);
56
+ }
57
+ let mut builder = protocol::CgroupsBuilder::new(response_buf, item_budget, 0, 0);
58
+ if !handler(&request, &mut builder) {
59
+ return Err(DispatchError::HandlerFailed);
60
+ }
61
+ let n = builder.finish();
62
+ if n == 0 {
63
+ return Err(DispatchError::Overflow);
64
+ }
65
+ Ok(n)
66
+ })
67
+}
src/crates/netipc/src/service/raw/client.rs
new
+272
@@ -0,0 +1,272 @@
1
+use super::common::{ensure_client_scratch, next_power_of_2_u32};
2
+use crate::protocol::{self, NipcError, MAX_PAYLOAD_CAP};
3
+
4
+#[cfg(unix)]
5
+pub(super) use crate::transport::posix::ClientConfig;
6
+
7
+#[cfg(unix)]
8
+use crate::transport::posix::UdsSession;
9
+
10
+#[cfg(target_os = "linux")]
11
+use crate::transport::shm::ShmContext;
12
+
13
+#[cfg(windows)]
14
+pub(super) use crate::transport::windows::ClientConfig;
15
+
16
+#[cfg(windows)]
17
+use crate::transport::windows::NpSession;
18
+
19
+#[cfg(windows)]
20
+use crate::transport::win_shm::WinShmContext;
21
+
22
+/// Client connection state machine.
23
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24
+pub enum ClientState {
25
+ Disconnected,
26
+ Connecting,
27
+ Ready,
28
+ NotFound,
29
+ AuthFailed,
30
+ Incompatible,
31
+ Broken,
32
+}
33
+
34
+/// Diagnostic counters snapshot.
35
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36
+pub struct ClientStatus {
37
+ pub state: ClientState,
38
+ pub connect_count: u32,
39
+ pub reconnect_count: u32,
40
+ pub call_count: u32,
41
+ pub error_count: u32,
42
+}
43
+
44
+/// L2 client context bound to one service kind.
45
+///
46
+/// Manages connection lifecycle and provides typed blocking calls with
47
+/// at-least-once retry semantics. The outer request code remains only for
48
+/// validation; each client instance is bound to one expected request kind.
49
+pub struct RawClient {
50
+ pub(super) state: ClientState,
51
+ pub(super) run_dir: String,
52
+ pub(super) service_name: String,
53
+ pub(super) expected_method_code: u16,
54
+ pub(super) transport_config: ClientConfig,
55
+
56
+ #[cfg(unix)]
57
+ pub(super) session: Option<UdsSession>,
58
+ #[cfg(target_os = "linux")]
59
+ pub(super) shm: Option<ShmContext>,
60
+
61
+ #[cfg(windows)]
62
+ pub(super) session: Option<NpSession>,
63
+ #[cfg(windows)]
64
+ pub(super) shm: Option<WinShmContext>,
65
+
66
+ pub(super) request_buf: Vec<u8>,
67
+ pub(super) send_buf: Vec<u8>,
68
+ pub(super) transport_buf: Vec<u8>,
69
+
70
+ pub(super) connect_count: u32,
71
+ pub(super) reconnect_count: u32,
72
+ pub(super) call_count: u32,
73
+ pub(super) error_count: u32,
74
+}
75
+
76
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77
+pub(super) struct RawCallKind {
78
+ pub(super) flags: u16,
79
+ pub(super) item_count: u32,
80
+ pub(super) check_item_count: bool,
81
+}
82
+
83
+impl RawCallKind {
84
+ pub(super) fn single() -> Self {
85
+ Self {
86
+ flags: 0,
87
+ item_count: 1,
88
+ check_item_count: false,
89
+ }
90
+ }
91
+
92
+ pub(super) fn batch(item_count: u32) -> Self {
93
+ Self {
94
+ flags: protocol::FLAG_BATCH,
95
+ item_count,
96
+ check_item_count: true,
97
+ }
98
+ }
99
+}
100
+
101
+impl RawClient {
102
+ pub(super) fn new_bound(
103
+ run_dir: &str,
104
+ service_name: &str,
105
+ expected_method_code: u16,
106
+ config: ClientConfig,
107
+ ) -> Self {
108
+ RawClient {
109
+ state: ClientState::Disconnected,
110
+ run_dir: run_dir.to_string(),
111
+ service_name: service_name.to_string(),
112
+ expected_method_code,
113
+ transport_config: config,
114
+ session: None,
115
+ #[cfg(target_os = "linux")]
116
+ shm: None,
117
+ #[cfg(windows)]
118
+ shm: None,
119
+ request_buf: Vec::new(),
120
+ send_buf: Vec::new(),
121
+ transport_buf: Vec::new(),
122
+ connect_count: 0,
123
+ reconnect_count: 0,
124
+ call_count: 0,
125
+ error_count: 0,
126
+ }
127
+ }
128
+
129
+ /// Attempt connect if DISCONNECTED/NOT_FOUND, reconnect if BROKEN.
130
+ /// Returns true if the state changed.
131
+ pub fn refresh(&mut self) -> bool {
132
+ let old_state = self.state;
133
+
134
+ match self.state {
135
+ ClientState::Disconnected | ClientState::NotFound => {
136
+ self.state = ClientState::Connecting;
137
+ self.state = self.try_connect();
138
+ if self.state == ClientState::Ready {
139
+ self.connect_count += 1;
140
+ }
141
+ }
142
+ ClientState::Broken => {
143
+ self.disconnect();
144
+ self.state = ClientState::Connecting;
145
+ self.state = self.try_connect();
146
+ if self.state == ClientState::Ready {
147
+ self.reconnect_count += 1;
148
+ }
149
+ }
150
+ ClientState::Ready
151
+ | ClientState::Connecting
152
+ | ClientState::AuthFailed
153
+ | ClientState::Incompatible => {}
154
+ }
155
+
156
+ self.state != old_state
157
+ }
158
+
159
+ /// Cheap cached boolean. No I/O, no syscalls.
160
+ #[inline]
161
+ pub fn ready(&self) -> bool {
162
+ self.state == ClientState::Ready
163
+ }
164
+
165
+ /// Detailed status snapshot for diagnostics.
166
+ pub fn status(&self) -> ClientStatus {
167
+ ClientStatus {
168
+ state: self.state,
169
+ connect_count: self.connect_count,
170
+ reconnect_count: self.reconnect_count,
171
+ call_count: self.call_count,
172
+ error_count: self.error_count,
173
+ }
174
+ }
175
+
176
+ pub(super) fn request_scratch(&mut self, needed: usize) -> &mut [u8] {
177
+ ensure_client_scratch(&mut self.request_buf, needed)
178
+ }
179
+
180
+ pub(super) fn validate_method(&self, method_code: u16) -> Result<(), NipcError> {
181
+ if self.expected_method_code == method_code {
182
+ Ok(())
183
+ } else {
184
+ Err(NipcError::BadLayout)
185
+ }
186
+ }
187
+
188
+ pub(super) fn session_max_request_payload_bytes(&self) -> u32 {
189
+ #[cfg(unix)]
190
+ if let Some(ref session) = self.session {
191
+ return session.max_request_payload_bytes;
192
+ }
193
+
194
+ #[cfg(windows)]
195
+ if let Some(ref session) = self.session {
196
+ return session.max_request_payload_bytes;
197
+ }
198
+
199
+ self.transport_config.max_request_payload_bytes
200
+ }
201
+
202
+ pub(super) fn session_max_response_payload_bytes(&self) -> u32 {
203
+ #[cfg(unix)]
204
+ if let Some(ref session) = self.session {
205
+ return session.max_response_payload_bytes;
206
+ }
207
+
208
+ #[cfg(windows)]
209
+ if let Some(ref session) = self.session {
210
+ return session.max_response_payload_bytes;
211
+ }
212
+
213
+ self.transport_config.max_response_payload_bytes
214
+ }
215
+
216
+ pub(super) fn client_note_request_capacity(&mut self, payload_len: u32) {
217
+ let grown = next_power_of_2_u32(payload_len).min(MAX_PAYLOAD_CAP);
218
+ if grown > self.transport_config.max_request_payload_bytes {
219
+ self.transport_config.max_request_payload_bytes = grown;
220
+ }
221
+ }
222
+
223
+ pub(super) fn client_note_response_capacity(&mut self, payload_len: u32) {
224
+ let grown = next_power_of_2_u32(payload_len).min(MAX_PAYLOAD_CAP);
225
+ if grown > self.transport_config.max_response_payload_bytes {
226
+ self.transport_config.max_response_payload_bytes = grown;
227
+ }
228
+ }
229
+
230
+ /// Tear down connection and release resources.
231
+ pub fn close(&mut self) {
232
+ self.disconnect();
233
+ self.state = ClientState::Disconnected;
234
+ }
235
+
236
+ /// Tear down the current connection.
237
+ pub(super) fn disconnect(&mut self) {
238
+ #[cfg(target_os = "linux")]
239
+ {
240
+ if let Some(mut shm) = self.shm.take() {
241
+ shm.close();
242
+ }
243
+ }
244
+
245
+ #[cfg(windows)]
246
+ {
247
+ if let Some(mut shm) = self.shm.take() {
248
+ shm.close();
249
+ }
250
+ }
251
+
252
+ self.session.take();
253
+ }
254
+}
255
+
256
+impl Drop for RawClient {
257
+ fn drop(&mut self) {
258
+ self.close();
259
+ }
260
+}
261
+
262
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263
+pub(super) enum ClientResponseSource {
264
+ TransportBuf,
265
+ SessionBuf,
266
+}
267
+
268
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269
+pub(super) struct ClientResponseRef {
270
+ pub(super) source: ClientResponseSource,
271
+ pub(super) len: usize,
272
+}
src/crates/netipc/src/service/raw/client_call.rs
new
+386
@@ -0,0 +1,386 @@
1
+use super::client::{ClientResponseRef, ClientResponseSource, ClientState, RawCallKind, RawClient};
2
+use super::common::{ensure_client_scratch, CACHE_RESPONSE_BUF_SIZE};
3
+use crate::protocol::{
4
+ self, Header, NipcError, HEADER_SIZE, KIND_REQUEST, KIND_RESPONSE, MAGIC_MSG,
5
+ STATUS_LIMIT_EXCEEDED, STATUS_OK, VERSION,
6
+};
7
+
8
+impl RawClient {
9
+ /// Reconnect-driven recovery for raw calls.
10
+ ///
11
+ /// Ordinary failures retry once. Overflow-driven resize recovery may
12
+ /// reconnect more than once while negotiated capacities grow.
13
+ pub(super) fn raw_call_with_retry(
14
+ &mut self,
15
+ method_code: u16,
16
+ req_len: usize,
17
+ call: RawCallKind,
18
+ ) -> Result<ClientResponseRef, NipcError> {
19
+ if self.state != ClientState::Ready {
20
+ self.error_count += 1;
21
+ return Err(NipcError::BadLayout);
22
+ }
23
+
24
+ let mut overflow_retries = 0u32;
25
+ loop {
26
+ let prev_req = self.session_max_request_payload_bytes();
27
+ let prev_resp = self.session_max_response_payload_bytes();
28
+ let prev_cfg_req = self.transport_config.max_request_payload_bytes;
29
+ let prev_cfg_resp = self.transport_config.max_response_payload_bytes;
30
+
31
+ match self.do_raw_call(method_code, req_len, call) {
32
+ Ok(payload) => {
33
+ self.call_count += 1;
34
+ return Ok(payload);
35
+ }
36
+ Err(first_err) => {
37
+ if first_err != NipcError::Overflow {
38
+ self.disconnect();
39
+ self.state = ClientState::Broken;
40
+ self.state = self.try_connect();
41
+ if self.state != ClientState::Ready {
42
+ self.error_count += 1;
43
+ return Err(first_err);
44
+ }
45
+ self.reconnect_count += 1;
46
+
47
+ match self.do_raw_call(method_code, req_len, call) {
48
+ Ok(payload) => {
49
+ self.call_count += 1;
50
+ return Ok(payload);
51
+ }
52
+ Err(retry_err) => {
53
+ self.disconnect();
54
+ self.state = ClientState::Broken;
55
+ self.error_count += 1;
56
+ return Err(retry_err);
57
+ }
58
+ }
59
+ }
60
+
61
+ self.disconnect();
62
+ self.state = ClientState::Broken;
63
+ self.state = self.try_connect();
64
+ if self.state != ClientState::Ready {
65
+ self.error_count += 1;
66
+ return Err(first_err);
67
+ }
68
+ self.reconnect_count += 1;
69
+
70
+ if self.session_max_request_payload_bytes() <= prev_req
71
+ && self.session_max_response_payload_bytes() <= prev_resp
72
+ && self.transport_config.max_request_payload_bytes <= prev_cfg_req
73
+ && self.transport_config.max_response_payload_bytes <= prev_cfg_resp
74
+ {
75
+ self.disconnect();
76
+ self.state = ClientState::Broken;
77
+ self.error_count += 1;
78
+ return Err(first_err);
79
+ }
80
+
81
+ overflow_retries += 1;
82
+ if overflow_retries >= 8 {
83
+ self.disconnect();
84
+ self.state = ClientState::Broken;
85
+ self.error_count += 1;
86
+ return Err(first_err);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ /// Single attempt at a raw call.
94
+ fn do_raw_call(
95
+ &mut self,
96
+ method_code: u16,
97
+ req_len: usize,
98
+ call: RawCallKind,
99
+ ) -> Result<ClientResponseRef, NipcError> {
100
+ let mut hdr = Header {
101
+ kind: KIND_REQUEST,
102
+ code: method_code,
103
+ flags: call.flags,
104
+ item_count: call.item_count,
105
+ message_id: (self.call_count as u64) + 1,
106
+ transport_status: STATUS_OK,
107
+ ..Header::default()
108
+ };
109
+
110
+ self.transport_send_request_buf(&mut hdr, req_len)?;
111
+ let (resp_hdr, response) = self.transport_receive()?;
112
+
113
+ if resp_hdr.kind != KIND_RESPONSE {
114
+ return Err(NipcError::BadKind);
115
+ }
116
+ if resp_hdr.code != method_code {
117
+ return Err(NipcError::BadLayout);
118
+ }
119
+ if resp_hdr.message_id != hdr.message_id {
120
+ return Err(NipcError::BadLayout);
121
+ }
122
+
123
+ match resp_hdr.transport_status {
124
+ STATUS_OK => {}
125
+ STATUS_LIMIT_EXCEEDED => {
126
+ let current = self.session_max_response_payload_bytes();
127
+ if current > 0 {
128
+ self.client_note_response_capacity(current.saturating_mul(2));
129
+ }
130
+ return Err(NipcError::Overflow);
131
+ }
132
+ _ => return Err(NipcError::BadLayout),
133
+ }
134
+
135
+ if call.check_item_count && resp_hdr.item_count != call.item_count {
136
+ return Err(NipcError::BadItemCount);
137
+ }
138
+
139
+ Ok(response)
140
+ }
141
+
142
+ /// Compatibility test seam for sending a borrowed payload through the
143
+ /// single shared request-buffer transport path.
144
+ #[cfg(test)]
145
+ #[allow(dead_code)]
146
+ pub(super) fn transport_send(
147
+ &mut self,
148
+ hdr: &mut Header,
149
+ payload: &[u8],
150
+ ) -> Result<(), NipcError> {
151
+ let req = self.request_scratch(payload.len());
152
+ req.copy_from_slice(payload);
153
+ self.transport_send_request_buf(hdr, payload.len())
154
+ }
155
+
156
+ /// Send via the active transport (SHM if available, baseline otherwise).
157
+ pub(super) fn transport_send_request_buf(
158
+ &mut self,
159
+ hdr: &mut Header,
160
+ req_len: usize,
161
+ ) -> Result<(), NipcError> {
162
+ let max_request_payload_bytes = self.session_max_request_payload_bytes();
163
+
164
+ #[cfg(target_os = "linux")]
165
+ {
166
+ if self.shm.is_some() {
167
+ if req_len > max_request_payload_bytes as usize {
168
+ self.client_note_request_capacity(req_len as u32);
169
+ return Err(NipcError::Overflow);
170
+ }
171
+
172
+ let msg_len = HEADER_SIZE + req_len;
173
+ let msg = ensure_client_scratch(&mut self.send_buf, msg_len);
174
+
175
+ hdr.magic = MAGIC_MSG;
176
+ hdr.version = VERSION;
177
+ hdr.header_len = protocol::HEADER_LEN;
178
+ hdr.payload_len = req_len as u32;
179
+
180
+ hdr.encode(&mut msg[..HEADER_SIZE]);
181
+ if req_len > 0 {
182
+ msg[HEADER_SIZE..HEADER_SIZE + req_len]
183
+ .copy_from_slice(&self.request_buf[..req_len]);
184
+ }
185
+
186
+ let send_result = self.shm.as_mut().unwrap().send(&msg[..msg_len]);
187
+ return match send_result {
188
+ Ok(()) => Ok(()),
189
+ Err(crate::transport::shm::ShmError::MsgTooLarge) => {
190
+ self.client_note_request_capacity(req_len as u32);
191
+ Err(NipcError::Overflow)
192
+ }
193
+ Err(_) => Err(NipcError::Truncated),
194
+ };
195
+ }
196
+ }
197
+
198
+ #[cfg(windows)]
199
+ {
200
+ if self.shm.is_some() {
201
+ if req_len > max_request_payload_bytes as usize {
202
+ self.client_note_request_capacity(req_len as u32);
203
+ return Err(NipcError::Overflow);
204
+ }
205
+
206
+ let msg_len = HEADER_SIZE + req_len;
207
+ let msg = ensure_client_scratch(&mut self.send_buf, msg_len);
208
+
209
+ hdr.magic = MAGIC_MSG;
210
+ hdr.version = VERSION;
211
+ hdr.header_len = protocol::HEADER_LEN;
212
+ hdr.payload_len = req_len as u32;
213
+
214
+ hdr.encode(&mut msg[..HEADER_SIZE]);
215
+ if req_len > 0 {
216
+ msg[HEADER_SIZE..HEADER_SIZE + req_len]
217
+ .copy_from_slice(&self.request_buf[..req_len]);
218
+ }
219
+
220
+ let send_result = self.shm.as_mut().unwrap().send(&msg[..msg_len]);
221
+ return match send_result {
222
+ Ok(()) => Ok(()),
223
+ Err(crate::transport::win_shm::WinShmError::MsgTooLarge) => {
224
+ self.client_note_request_capacity(req_len as u32);
225
+ Err(NipcError::Overflow)
226
+ }
227
+ Err(_) => Err(NipcError::Truncated),
228
+ };
229
+ }
230
+ }
231
+
232
+ let send_result = {
233
+ let session = self.session.as_mut().ok_or(NipcError::Truncated)?;
234
+ session.send(hdr, &self.request_buf[..req_len])
235
+ };
236
+ match send_result {
237
+ Ok(()) => Ok(()),
238
+ #[cfg(unix)]
239
+ Err(crate::transport::posix::UdsError::LimitExceeded) => {
240
+ self.client_note_request_capacity(req_len as u32);
241
+ Err(NipcError::Overflow)
242
+ }
243
+ #[cfg(windows)]
244
+ Err(crate::transport::windows::NpError::LimitExceeded) => {
245
+ self.client_note_request_capacity(req_len as u32);
246
+ Err(NipcError::Overflow)
247
+ }
248
+ Err(_) => Err(NipcError::Truncated),
249
+ }
250
+ }
251
+
252
+ /// Receive via the active transport. Returns (header, payload view).
253
+ pub(super) fn transport_receive(&mut self) -> Result<(Header, ClientResponseRef), NipcError> {
254
+ let needed = self.max_receive_message_bytes();
255
+ let scratch = ensure_client_scratch(&mut self.transport_buf, needed);
256
+
257
+ #[cfg(target_os = "linux")]
258
+ {
259
+ if let Some(ref mut shm) = self.shm {
260
+ let mlen = shm
261
+ .receive(scratch, 30000)
262
+ .map_err(|_| NipcError::Truncated)?;
263
+
264
+ if mlen < HEADER_SIZE {
265
+ return Err(NipcError::Truncated);
266
+ }
267
+
268
+ let hdr = Header::decode(&scratch[..mlen])?;
269
+ return Ok((
270
+ hdr,
271
+ ClientResponseRef {
272
+ source: ClientResponseSource::TransportBuf,
273
+ len: mlen - HEADER_SIZE,
274
+ },
275
+ ));
276
+ }
277
+ }
278
+
279
+ #[cfg(windows)]
280
+ {
281
+ if let Some(ref mut shm) = self.shm {
282
+ let mlen = shm
283
+ .receive(scratch, 30000)
284
+ .map_err(|_| NipcError::Truncated)?;
285
+
286
+ if mlen < HEADER_SIZE {
287
+ return Err(NipcError::Truncated);
288
+ }
289
+
290
+ let hdr = Header::decode(&scratch[..mlen])?;
291
+ return Ok((
292
+ hdr,
293
+ ClientResponseRef {
294
+ source: ClientResponseSource::TransportBuf,
295
+ len: mlen - HEADER_SIZE,
296
+ },
297
+ ));
298
+ }
299
+ }
300
+
301
+ let session = self.session.as_mut().ok_or(NipcError::Truncated)?;
302
+
303
+ #[cfg(unix)]
304
+ {
305
+ let scratch_payload_ptr = unsafe { scratch.as_ptr().add(HEADER_SIZE) };
306
+ let (hdr, payload) = session.receive(scratch).map_err(|_| NipcError::Truncated)?;
307
+ let source = if payload.as_ptr() == scratch_payload_ptr {
308
+ ClientResponseSource::TransportBuf
309
+ } else {
310
+ ClientResponseSource::SessionBuf
311
+ };
312
+ Ok((
313
+ hdr,
314
+ ClientResponseRef {
315
+ source,
316
+ len: payload.len(),
317
+ },
318
+ ))
319
+ }
320
+
321
+ #[cfg(windows)]
322
+ {
323
+ let scratch_payload_ptr = unsafe { scratch.as_ptr().add(HEADER_SIZE) };
324
+ let (hdr, payload) = session.receive(scratch).map_err(|_| NipcError::Truncated)?;
325
+ let source = if payload.as_ptr() == scratch_payload_ptr {
326
+ ClientResponseSource::TransportBuf
327
+ } else {
328
+ ClientResponseSource::SessionBuf
329
+ };
330
+ Ok((
331
+ hdr,
332
+ ClientResponseRef {
333
+ source,
334
+ len: payload.len(),
335
+ },
336
+ ))
337
+ }
338
+ }
339
+
340
+ pub(super) fn response_payload(&self, response: ClientResponseRef) -> Result<&[u8], NipcError> {
341
+ match response.source {
342
+ ClientResponseSource::TransportBuf => {
343
+ let start = HEADER_SIZE;
344
+ let end = HEADER_SIZE + response.len;
345
+ if end > self.transport_buf.len() {
346
+ return Err(NipcError::Truncated);
347
+ }
348
+ Ok(&self.transport_buf[start..end])
349
+ }
350
+ ClientResponseSource::SessionBuf => {
351
+ #[cfg(unix)]
352
+ {
353
+ let session = self.session.as_ref().ok_or(NipcError::Truncated)?;
354
+ return Ok(session.received_payload(response.len));
355
+ }
356
+ #[cfg(windows)]
357
+ {
358
+ let session = self.session.as_ref().ok_or(NipcError::Truncated)?;
359
+ return Ok(session.received_payload(response.len));
360
+ }
361
+ #[allow(unreachable_code)]
362
+ Err(NipcError::Truncated)
363
+ }
364
+ }
365
+ }
366
+
367
+ pub(super) fn max_receive_message_bytes(&self) -> usize {
368
+ let mut max_payload = self.transport_config.max_response_payload_bytes as usize;
369
+ #[cfg(unix)]
370
+ if let Some(ref session) = self.session {
371
+ if session.max_response_payload_bytes > 0 {
372
+ max_payload = session.max_response_payload_bytes as usize;
373
+ }
374
+ }
375
+ #[cfg(windows)]
376
+ if let Some(ref session) = self.session {
377
+ if session.max_response_payload_bytes > 0 {
378
+ max_payload = session.max_response_payload_bytes as usize;
379
+ }
380
+ }
381
+ if max_payload == 0 {
382
+ max_payload = CACHE_RESPONSE_BUF_SIZE;
383
+ }
384
+ HEADER_SIZE + max_payload
385
+ }
386
+}
src/crates/netipc/src/service/raw/client_unix.rs
new
+81
@@ -0,0 +1,81 @@
1
+#![cfg(unix)]
2
+
3
+use super::client::{ClientState, RawClient};
4
+use super::common::{CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS, CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS};
5
+#[cfg(target_os = "linux")]
6
+use crate::protocol::{PROFILE_SHM_FUTEX, PROFILE_SHM_HYBRID};
7
+use crate::transport::posix::UdsSession;
8
+#[cfg(target_os = "linux")]
9
+use crate::transport::shm::ShmContext;
10
+
11
+impl RawClient {
12
+ /// Attempt a full connection: transport connect + handshake, then SHM
13
+ /// upgrade if negotiated.
14
+ #[cfg(unix)]
15
+ pub(super) fn try_connect(&mut self) -> ClientState {
16
+ match UdsSession::connect(&self.run_dir, &self.service_name, &self.transport_config) {
17
+ Ok(session) => {
18
+ #[cfg(target_os = "linux")]
19
+ let selected_profile = session.selected_profile;
20
+ #[cfg(target_os = "linux")]
21
+ let session_id = session.session_id;
22
+
23
+ #[cfg(target_os = "linux")]
24
+ {
25
+ if selected_profile == PROFILE_SHM_HYBRID
26
+ || selected_profile == PROFILE_SHM_FUTEX
27
+ {
28
+ let mut shm_ok = false;
29
+ let deadline = std::time::Instant::now()
30
+ + std::time::Duration::from_millis(CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS);
31
+ loop {
32
+ match ShmContext::client_attach(
33
+ &self.run_dir,
34
+ &self.service_name,
35
+ session_id,
36
+ ) {
37
+ Ok(ctx) => {
38
+ self.shm = Some(ctx);
39
+ shm_ok = true;
40
+ break;
41
+ }
42
+ Err(_) => {
43
+ if std::time::Instant::now() >= deadline {
44
+ break;
45
+ }
46
+ std::thread::sleep(std::time::Duration::from_millis(
47
+ CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS,
48
+ ));
49
+ }
50
+ }
51
+ }
52
+ if !shm_ok {
53
+ drop(session);
54
+ self.transport_config.supported_profiles &=
55
+ !(PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
56
+ self.transport_config.preferred_profiles &=
57
+ !(PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
58
+ if self.transport_config.supported_profiles == 0 {
59
+ return ClientState::Disconnected;
60
+ }
61
+ return self.try_connect();
62
+ }
63
+ }
64
+ }
65
+
66
+ self.session = Some(session);
67
+ ClientState::Ready
68
+ }
69
+ Err(e) => {
70
+ use crate::transport::posix::UdsError;
71
+ match e {
72
+ UdsError::Connect(_) => ClientState::NotFound,
73
+ UdsError::AuthFailed => ClientState::AuthFailed,
74
+ UdsError::NoProfile => ClientState::Incompatible,
75
+ UdsError::Incompatible(_) => ClientState::Incompatible,
76
+ _ => ClientState::Disconnected,
77
+ }
78
+ }
79
+ }
80
+ }
81
+}
src/crates/netipc/src/service/raw/client_windows.rs
new
+70
@@ -0,0 +1,70 @@
1
+use super::client::{ClientState, RawClient};
2
+use super::common::{CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS, CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS};
3
+use crate::transport::win_shm::{
4
+ WinShmContext, PROFILE_BUSYWAIT as WIN_SHM_PROFILE_BUSYWAIT,
5
+ PROFILE_HYBRID as WIN_SHM_PROFILE_HYBRID,
6
+};
7
+use crate::transport::windows::{NpError, NpSession};
8
+
9
+impl RawClient {
10
+ /// Windows: attempt a full Named Pipe connection + Win SHM upgrade.
11
+ pub(super) fn try_connect(&mut self) -> ClientState {
12
+ match NpSession::connect(&self.run_dir, &self.service_name, &self.transport_config) {
13
+ Ok(session) => {
14
+ let selected_profile = session.selected_profile;
15
+
16
+ if selected_profile == WIN_SHM_PROFILE_HYBRID
17
+ || selected_profile == WIN_SHM_PROFILE_BUSYWAIT
18
+ {
19
+ let mut shm_ok = false;
20
+ let deadline = std::time::Instant::now()
21
+ + std::time::Duration::from_millis(CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS);
22
+ loop {
23
+ match WinShmContext::client_attach(
24
+ &self.run_dir,
25
+ &self.service_name,
26
+ self.transport_config.auth_token,
27
+ session.session_id,
28
+ selected_profile,
29
+ ) {
30
+ Ok(ctx) => {
31
+ self.shm = Some(ctx);
32
+ shm_ok = true;
33
+ break;
34
+ }
35
+ Err(_) => {
36
+ if std::time::Instant::now() >= deadline {
37
+ break;
38
+ }
39
+ std::thread::sleep(std::time::Duration::from_millis(
40
+ CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS,
41
+ ));
42
+ }
43
+ }
44
+ }
45
+ if !shm_ok {
46
+ drop(session);
47
+ self.transport_config.supported_profiles &=
48
+ !(WIN_SHM_PROFILE_HYBRID | WIN_SHM_PROFILE_BUSYWAIT);
49
+ self.transport_config.preferred_profiles &=
50
+ !(WIN_SHM_PROFILE_HYBRID | WIN_SHM_PROFILE_BUSYWAIT);
51
+ if self.transport_config.supported_profiles == 0 {
52
+ return ClientState::Disconnected;
53
+ }
54
+ return self.try_connect();
55
+ }
56
+ }
57
+
58
+ self.session = Some(session);
59
+ ClientState::Ready
60
+ }
61
+ Err(e) => match e {
62
+ NpError::Connect(_) => ClientState::NotFound,
63
+ NpError::AuthFailed => ClientState::AuthFailed,
64
+ NpError::NoProfile => ClientState::Incompatible,
65
+ NpError::Incompatible(_) => ClientState::Incompatible,
66
+ _ => ClientState::Disconnected,
67
+ },
68
+ }
69
+ }
70
+}
src/crates/netipc/src/service/raw/common.rs
new
+29
@@ -0,0 +1,29 @@
1
+pub(super) const SERVER_POLL_TIMEOUT_MS: u32 = 100;
2
+pub(super) const CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS: u64 = 5;
3
+pub(super) const CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS: u64 = 5_000;
4
+pub(super) const CACHE_RESPONSE_BUF_SIZE: usize = 65536;
5
+
6
+pub(super) fn next_power_of_2_u32(n: u32) -> u32 {
7
+ if n < 16 {
8
+ return 16;
9
+ }
10
+
11
+ if n > (1u32 << 31) {
12
+ return 1u32 << 31;
13
+ }
14
+
15
+ let mut value = n - 1;
16
+ value |= value >> 1;
17
+ value |= value >> 2;
18
+ value |= value >> 4;
19
+ value |= value >> 8;
20
+ value |= value >> 16;
21
+ value + 1
22
+}
23
+
24
+pub(super) fn ensure_client_scratch(buf: &mut Vec<u8>, needed: usize) -> &mut [u8] {
25
+ if buf.len() < needed {
26
+ buf.resize(needed, 0);
27
+ }
28
+ &mut buf[..needed]
29
+}
src/crates/netipc/src/service/raw/dispatch.rs
new
+71
@@ -0,0 +1,71 @@
1
+use super::common::next_power_of_2_u32;
2
+use std::sync::atomic::{AtomicU32, Ordering};
3
+use std::sync::Arc;
4
+
5
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6
+pub enum DispatchError {
7
+ BadEnvelope,
8
+ Overflow,
9
+ HandlerFailed,
10
+}
11
+
12
+pub type DispatchHandler =
13
+ Arc<dyn Fn(&[u8], &mut [u8]) -> Result<usize, DispatchError> + Send + Sync>;
14
+
15
+pub(super) fn dispatch_single_internal(
16
+ expected_method_code: u16,
17
+ handler: Option<&DispatchHandler>,
18
+ method_code: u16,
19
+ request: &[u8],
20
+ response_buf: &mut [u8],
21
+) -> Result<usize, DispatchError> {
22
+ if method_code != expected_method_code {
23
+ return Err(DispatchError::HandlerFailed);
24
+ }
25
+
26
+ match handler {
27
+ Some(dispatch) => match dispatch(request, response_buf) {
28
+ Ok(n) if n <= response_buf.len() => Ok(n),
29
+ Ok(_) => Err(DispatchError::Overflow),
30
+ Err(err) => Err(err),
31
+ },
32
+ None => Err(DispatchError::HandlerFailed),
33
+ }
34
+}
35
+
36
+#[cfg(test)]
37
+#[allow(dead_code)]
38
+pub(super) fn dispatch_single(
39
+ expected_method_code: u16,
40
+ handler: Option<&DispatchHandler>,
41
+ method_code: u16,
42
+ request: &[u8],
43
+ response_buf: &mut [u8],
44
+) -> Result<usize, DispatchError> {
45
+ dispatch_single_internal(
46
+ expected_method_code,
47
+ handler,
48
+ method_code,
49
+ request,
50
+ response_buf,
51
+ )
52
+}
53
+
54
+pub(super) fn method_supported_internal(
55
+ expected_method_code: u16,
56
+ handler: Option<&DispatchHandler>,
57
+ method_code: u16,
58
+) -> bool {
59
+ handler.is_some() && method_code == expected_method_code
60
+}
61
+
62
+pub(super) fn server_note_payload_capacity(target: &AtomicU32, payload_len: u32) {
63
+ let grown = next_power_of_2_u32(payload_len);
64
+ let mut current = target.load(Ordering::Relaxed);
65
+ while grown > current {
66
+ match target.compare_exchange_weak(current, grown, Ordering::Release, Ordering::Relaxed) {
67
+ Ok(_) => break,
68
+ Err(observed) => current = observed,
69
+ }
70
+ }
71
+}
src/crates/netipc/src/service/raw/increment.rs
new
+91
@@ -0,0 +1,91 @@
1
+use super::client::{ClientConfig, RawCallKind, RawClient};
2
+use super::dispatch::{DispatchError, DispatchHandler};
3
+use crate::protocol::{
4
+ self, batch_item_get, increment_decode, increment_encode, BatchBuilder, NipcError,
5
+ INCREMENT_PAYLOAD_SIZE, METHOD_INCREMENT,
6
+};
7
+use std::sync::Arc;
8
+
9
+pub type IncrementHandler = Arc<dyn Fn(u64) -> Option<u64> + Send + Sync>;
10
+
11
+impl RawClient {
12
+ /// Create a new client context bound to the increment service kind.
13
+ /// Does NOT connect. Does NOT require the server to be running.
14
+ pub fn new_increment(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
15
+ Self::new_bound(run_dir, service_name, METHOD_INCREMENT, config)
16
+ }
17
+
18
+ /// Blocking typed call: INCREMENT method.
19
+ /// Sends a u64 value, receives the incremented u64 back.
20
+ pub fn call_increment(&mut self, value: u64) -> Result<u64, NipcError> {
21
+ self.validate_method(METHOD_INCREMENT)?;
22
+ let req_len = {
23
+ let req_buf = self.request_scratch(INCREMENT_PAYLOAD_SIZE);
24
+ let req_len = increment_encode(value, req_buf);
25
+ if req_len == 0 {
26
+ return Err(NipcError::Truncated);
27
+ }
28
+ req_len
29
+ };
30
+
31
+ let response =
32
+ self.raw_call_with_retry(METHOD_INCREMENT, req_len, RawCallKind::single())?;
33
+ increment_decode(self.response_payload(response)?)
34
+ }
35
+
36
+ /// Blocking typed batch call: INCREMENT method.
37
+ /// Sends multiple u64 values, receives the incremented u64s back.
38
+ pub fn call_increment_batch(&mut self, values: &[u64]) -> Result<Vec<u64>, NipcError> {
39
+ self.validate_method(METHOD_INCREMENT)?;
40
+ if values.is_empty() {
41
+ return Ok(Vec::new());
42
+ }
43
+
44
+ if values.len() == 1 {
45
+ let r = self.call_increment(values[0])?;
46
+ return Ok(vec![r]);
47
+ }
48
+
49
+ let count = values.len() as u32;
50
+ let req_buf_size = protocol::align8(count as usize * 8)
51
+ + count as usize * protocol::align8(INCREMENT_PAYLOAD_SIZE)
52
+ + 64;
53
+ let req_len = {
54
+ let req_buf = self.request_scratch(req_buf_size);
55
+ let mut bb = BatchBuilder::new(req_buf, count);
56
+ for &v in values {
57
+ let mut item_buf = [0u8; INCREMENT_PAYLOAD_SIZE];
58
+ if increment_encode(v, &mut item_buf) == 0 {
59
+ return Err(NipcError::Truncated);
60
+ }
61
+ bb.add(&item_buf).map_err(|_| NipcError::Overflow)?;
62
+ }
63
+ let (req_len, _out_count) = bb.finish();
64
+ req_len
65
+ };
66
+
67
+ let response =
68
+ self.raw_call_with_retry(METHOD_INCREMENT, req_len, RawCallKind::batch(count))?;
69
+ let resp_payload = self.response_payload(response)?;
70
+ let mut results = Vec::with_capacity(values.len());
71
+ for i in 0..count {
72
+ let (item_data, _item_len) = batch_item_get(resp_payload, count, i)?;
73
+ let val = increment_decode(item_data)?;
74
+ results.push(val);
75
+ }
76
+
77
+ Ok(results)
78
+ }
79
+}
80
+
81
+pub fn increment_dispatch(handler: IncrementHandler) -> DispatchHandler {
82
+ Arc::new(move |request, response_buf| {
83
+ let value = increment_decode(request).map_err(|_| DispatchError::BadEnvelope)?;
84
+ let result = handler(value).ok_or(DispatchError::HandlerFailed)?;
85
+ let n = increment_encode(result, response_buf);
86
+ if n == 0 {
87
+ return Err(DispatchError::Overflow);
88
+ }
89
+ Ok(n)
90
+ })
91
+}
src/crates/netipc/src/service/raw/server.rs
new
+113
@@ -0,0 +1,113 @@
1
+use super::dispatch::DispatchHandler;
2
+use crate::protocol::MAX_PAYLOAD_DEFAULT;
3
+
4
+#[cfg(unix)]
5
+pub(super) use crate::transport::posix::ServerConfig;
6
+
7
+#[cfg(windows)]
8
+pub(super) use crate::transport::windows::ServerConfig;
9
+
10
+use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
11
+use std::sync::Arc;
12
+
13
+/// L2 managed server. Typed request/response dispatcher.
14
+///
15
+/// Handles accept, spawns a thread per session (up to worker_count),
16
+/// reads requests, dispatches to handler, sends responses.
17
+pub struct ManagedServer {
18
+ pub(super) run_dir: String,
19
+ pub(super) service_name: String,
20
+ pub(super) server_config: ServerConfig,
21
+ pub(super) expected_method_code: u16,
22
+ pub(super) handler: Option<DispatchHandler>,
23
+ pub(super) running: Arc<AtomicBool>,
24
+ pub(super) learned_request_payload_bytes: Arc<AtomicU32>,
25
+ pub(super) learned_response_payload_bytes: Arc<AtomicU32>,
26
+ pub(super) next_session_id: u64,
27
+ pub(super) worker_count: usize,
28
+ #[cfg(windows)]
29
+ pub(super) listener_handle: Arc<std::sync::Mutex<Option<usize>>>,
30
+}
31
+
32
+impl ManagedServer {
33
+ /// Create a new managed server for a single service kind.
34
+ pub fn new(
35
+ run_dir: &str,
36
+ service_name: &str,
37
+ config: ServerConfig,
38
+ expected_method_code: u16,
39
+ handler: Option<DispatchHandler>,
40
+ ) -> Self {
41
+ Self::with_workers(
42
+ run_dir,
43
+ service_name,
44
+ config,
45
+ expected_method_code,
46
+ handler,
47
+ 8,
48
+ )
49
+ }
50
+
51
+ /// Create a managed server with an explicit worker count.
52
+ pub fn with_workers(
53
+ run_dir: &str,
54
+ service_name: &str,
55
+ config: ServerConfig,
56
+ expected_method_code: u16,
57
+ handler: Option<DispatchHandler>,
58
+ worker_count: usize,
59
+ ) -> Self {
60
+ let learned_request = if config.max_request_payload_bytes != 0 {
61
+ config.max_request_payload_bytes
62
+ } else {
63
+ MAX_PAYLOAD_DEFAULT
64
+ };
65
+ let learned_response = if config.max_response_payload_bytes != 0 {
66
+ config.max_response_payload_bytes
67
+ } else {
68
+ MAX_PAYLOAD_DEFAULT
69
+ };
70
+
71
+ ManagedServer {
72
+ run_dir: run_dir.to_string(),
73
+ service_name: service_name.to_string(),
74
+ server_config: config,
75
+ expected_method_code,
76
+ handler,
77
+ running: Arc::new(AtomicBool::new(false)),
78
+ learned_request_payload_bytes: Arc::new(AtomicU32::new(learned_request)),
79
+ learned_response_payload_bytes: Arc::new(AtomicU32::new(learned_response)),
80
+ next_session_id: 1,
81
+ worker_count: if worker_count < 1 { 1 } else { worker_count },
82
+ #[cfg(windows)]
83
+ listener_handle: Arc::new(std::sync::Mutex::new(None)),
84
+ }
85
+ }
86
+
87
+ /// Signal shutdown. On Windows, also closes the listener pipe to
88
+ /// unblock ConnectNamedPipe in the accept loop.
89
+ pub fn stop(&self) {
90
+ self.running.store(false, Ordering::Release);
91
+
92
+ #[cfg(windows)]
93
+ {
94
+ let mut guard = self.listener_handle.lock().unwrap();
95
+ if let Some(h) = guard.take() {
96
+ extern "system" {
97
+ fn CloseHandle(h: isize) -> i32;
98
+ }
99
+ unsafe {
100
+ CloseHandle(h as isize);
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ /// Returns the internal running flag for diagnostics and test helpers.
107
+ ///
108
+ /// For reliable shutdown, call `stop()`. On Windows, flipping this flag
109
+ /// alone does not wake a blocking listener accept.
110
+ pub fn running_flag(&self) -> Arc<AtomicBool> {
111
+ self.running.clone()
112
+ }
113
+}
src/crates/netipc/src/service/raw/server_session_unix.rs
new
+304
@@ -0,0 +1,304 @@
1
+use super::common::{ensure_client_scratch, SERVER_POLL_TIMEOUT_MS};
2
+use super::dispatch::{
3
+ dispatch_single_internal, method_supported_internal, server_note_payload_capacity,
4
+ DispatchError, DispatchHandler,
5
+};
6
+use crate::protocol::{
7
+ self, batch_item_get, BatchBuilder, Header, FLAG_BATCH, HEADER_SIZE, KIND_REQUEST,
8
+ KIND_RESPONSE, MAGIC_MSG, STATUS_BAD_ENVELOPE, STATUS_INTERNAL_ERROR, STATUS_LIMIT_EXCEEDED,
9
+ STATUS_OK, VERSION,
10
+};
11
+use crate::transport::posix::UdsSession;
12
+
13
+#[cfg(target_os = "linux")]
14
+use crate::transport::shm::ShmContext;
15
+
16
+use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
17
+use std::sync::Arc;
18
+
19
+/// POSIX: Handle one client session in its own thread.
20
+pub(super) fn handle_session_threaded(
21
+ mut session: UdsSession,
22
+ #[cfg(target_os = "linux")] mut shm: Option<ShmContext>,
23
+ #[cfg(not(target_os = "linux"))] _shm: Option<()>,
24
+ expected_method_code: u16,
25
+ handler: Option<DispatchHandler>,
26
+ running: Arc<AtomicBool>,
27
+ learned_request_payload_bytes: Arc<AtomicU32>,
28
+ learned_response_payload_bytes: Arc<AtomicU32>,
29
+) {
30
+ let mut recv_buf = vec![0u8; HEADER_SIZE + session.max_request_payload_bytes as usize];
31
+ let mut resp_buf = vec![0u8; session.max_response_payload_bytes as usize];
32
+ let mut item_resp_buf = vec![0u8; session.max_response_payload_bytes as usize];
33
+ let mut msg_buf = vec![0u8; HEADER_SIZE + session.max_response_payload_bytes as usize];
34
+
35
+ while running.load(Ordering::Acquire) {
36
+ let (hdr, payload) = {
37
+ #[cfg(target_os = "linux")]
38
+ {
39
+ if let Some(ref mut shm_ctx) = shm {
40
+ match shm_ctx.receive(&mut recv_buf, SERVER_POLL_TIMEOUT_MS) {
41
+ Ok(mlen) => {
42
+ if mlen < HEADER_SIZE {
43
+ break;
44
+ }
45
+ let hdr = match Header::decode(&recv_buf[..mlen]) {
46
+ Ok(h) => h,
47
+ Err(_) => break,
48
+ };
49
+ let payload = &recv_buf[HEADER_SIZE..mlen];
50
+ (hdr, payload)
51
+ }
52
+ Err(crate::transport::shm::ShmError::Timeout) => continue,
53
+ Err(_) => break,
54
+ }
55
+ } else {
56
+ let ready = poll_fd(session.fd(), SERVER_POLL_TIMEOUT_MS as i32);
57
+ if ready < 0 {
58
+ break;
59
+ }
60
+ if ready == 0 {
61
+ continue;
62
+ }
63
+
64
+ match session.receive(&mut recv_buf) {
65
+ Ok((hdr, payload)) => (hdr, payload),
66
+ Err(_) => break,
67
+ }
68
+ }
69
+ }
70
+
71
+ #[cfg(not(target_os = "linux"))]
72
+ {
73
+ let ready = poll_fd(session.fd(), SERVER_POLL_TIMEOUT_MS as i32);
74
+ if ready < 0 {
75
+ break;
76
+ }
77
+ if ready == 0 {
78
+ continue;
79
+ }
80
+
81
+ match session.receive(&mut recv_buf) {
82
+ Ok((hdr, payload)) => (hdr, payload),
83
+ Err(_) => break,
84
+ }
85
+ }
86
+ };
87
+
88
+ if hdr.kind != KIND_REQUEST {
89
+ break;
90
+ }
91
+
92
+ if payload.len() <= u32::MAX as usize {
93
+ server_note_payload_capacity(&learned_request_payload_bytes, payload.len() as u32);
94
+ }
95
+
96
+ if !method_supported_internal(expected_method_code, handler.as_ref(), hdr.code) {
97
+ let mut resp_hdr = Header {
98
+ kind: KIND_RESPONSE,
99
+ code: hdr.code,
100
+ message_id: hdr.message_id,
101
+ transport_status: protocol::STATUS_UNSUPPORTED,
102
+ item_count: 1,
103
+ ..Header::default()
104
+ };
105
+
106
+ #[cfg(target_os = "linux")]
107
+ {
108
+ if let Some(ref mut shm_ctx) = shm {
109
+ let msg = ensure_client_scratch(&mut msg_buf, HEADER_SIZE);
110
+ resp_hdr.magic = MAGIC_MSG;
111
+ resp_hdr.version = VERSION;
112
+ resp_hdr.header_len = protocol::HEADER_LEN;
113
+ resp_hdr.payload_len = 0;
114
+ resp_hdr.encode(&mut msg[..HEADER_SIZE]);
115
+ if shm_ctx.send(&msg[..HEADER_SIZE]).is_err() {
116
+ break;
117
+ }
118
+ continue;
119
+ }
120
+ }
121
+
122
+ if session.send(&mut resp_hdr, &[]).is_err() {
123
+ break;
124
+ }
125
+ continue;
126
+ }
127
+
128
+ let is_batch = (hdr.flags & FLAG_BATCH) != 0 && hdr.item_count >= 1;
129
+ let response_len;
130
+ let dispatch_result = if !is_batch {
131
+ dispatch_single_internal(
132
+ expected_method_code,
133
+ handler.as_ref(),
134
+ hdr.code,
135
+ payload,
136
+ &mut resp_buf,
137
+ )
138
+ } else {
139
+ let mut bb = BatchBuilder::new(&mut resp_buf, hdr.item_count);
140
+ let mut batch_result = Ok(0usize);
141
+
142
+ for i in 0..hdr.item_count {
143
+ let (item_data, _item_len) = match batch_item_get(payload, hdr.item_count, i) {
144
+ Ok(v) => v,
145
+ Err(_) => {
146
+ batch_result = Err(DispatchError::BadEnvelope);
147
+ break;
148
+ }
149
+ };
150
+ let item_len = match dispatch_single_internal(
151
+ expected_method_code,
152
+ handler.as_ref(),
153
+ hdr.code,
154
+ item_data,
155
+ &mut item_resp_buf,
156
+ ) {
157
+ Ok(n) => n,
158
+ Err(err) => {
159
+ batch_result = Err(err);
160
+ break;
161
+ }
162
+ };
163
+ if bb.add(&item_resp_buf[..item_len]).is_err() {
164
+ batch_result = Err(DispatchError::Overflow);
165
+ break;
166
+ }
167
+ }
168
+ if batch_result.is_ok() {
169
+ let (n, _) = bb.finish();
170
+ batch_result = Ok(n);
171
+ }
172
+ batch_result
173
+ };
174
+
175
+ let mut resp_hdr = Header {
176
+ kind: KIND_RESPONSE,
177
+ code: hdr.code,
178
+ message_id: hdr.message_id,
179
+ ..Header::default()
180
+ };
181
+
182
+ match dispatch_result {
183
+ Ok(n) => {
184
+ response_len = n;
185
+ if response_len <= u32::MAX as usize {
186
+ server_note_payload_capacity(
187
+ &learned_response_payload_bytes,
188
+ response_len as u32,
189
+ );
190
+ }
191
+ resp_hdr.transport_status = STATUS_OK;
192
+ if is_batch {
193
+ resp_hdr.flags = FLAG_BATCH;
194
+ resp_hdr.item_count = hdr.item_count;
195
+ } else {
196
+ resp_hdr.flags = 0;
197
+ resp_hdr.item_count = 1;
198
+ }
199
+ }
200
+ Err(DispatchError::Overflow) => {
201
+ let current = session.max_response_payload_bytes;
202
+ if current >= u32::MAX / 2 {
203
+ server_note_payload_capacity(&learned_response_payload_bytes, u32::MAX);
204
+ } else {
205
+ server_note_payload_capacity(&learned_response_payload_bytes, current * 2);
206
+ }
207
+ resp_hdr.transport_status = STATUS_LIMIT_EXCEEDED;
208
+ resp_hdr.item_count = 1;
209
+ resp_hdr.flags = 0;
210
+ response_len = 0;
211
+ }
212
+ Err(DispatchError::BadEnvelope) => {
213
+ resp_hdr.transport_status = STATUS_BAD_ENVELOPE;
214
+ resp_hdr.item_count = 1;
215
+ resp_hdr.flags = 0;
216
+ response_len = 0;
217
+ }
218
+ Err(DispatchError::HandlerFailed) => {
219
+ resp_hdr.transport_status = STATUS_INTERNAL_ERROR;
220
+ resp_hdr.item_count = 1;
221
+ resp_hdr.flags = 0;
222
+ response_len = 0;
223
+ }
224
+ }
225
+
226
+ #[cfg(target_os = "linux")]
227
+ {
228
+ if let Some(ref mut shm_ctx) = shm {
229
+ let msg_len = HEADER_SIZE + response_len;
230
+ let msg = ensure_client_scratch(&mut msg_buf, msg_len);
231
+
232
+ resp_hdr.magic = MAGIC_MSG;
233
+ resp_hdr.version = VERSION;
234
+ resp_hdr.header_len = protocol::HEADER_LEN;
235
+ resp_hdr.payload_len = response_len as u32;
236
+
237
+ resp_hdr.encode(&mut msg[..HEADER_SIZE]);
238
+ if response_len > 0 {
239
+ msg[HEADER_SIZE..].copy_from_slice(&resp_buf[..response_len]);
240
+ }
241
+
242
+ if shm_ctx.send(msg).is_err() {
243
+ break;
244
+ }
245
+ if resp_hdr.transport_status == STATUS_LIMIT_EXCEEDED {
246
+ break;
247
+ }
248
+ continue;
249
+ }
250
+ }
251
+
252
+ if session
253
+ .send(&mut resp_hdr, &resp_buf[..response_len])
254
+ .is_err()
255
+ {
256
+ break;
257
+ }
258
+ if resp_hdr.transport_status == STATUS_LIMIT_EXCEEDED {
259
+ break;
260
+ }
261
+ }
262
+
263
+ #[cfg(target_os = "linux")]
264
+ {
265
+ if let Some(mut shm_ctx) = shm {
266
+ shm_ctx.destroy();
267
+ }
268
+ }
269
+ drop(session);
270
+}
271
+
272
+/// Poll a file descriptor for readability with a timeout in milliseconds.
273
+/// Returns: 1 = data ready, 0 = timeout, -1 = error/hangup.
274
+pub(super) fn poll_fd(fd: i32, timeout_ms: i32) -> i32 {
275
+ let mut pfd = libc::pollfd {
276
+ fd,
277
+ events: libc::POLLIN,
278
+ revents: 0,
279
+ };
280
+
281
+ let ret = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
282
+
283
+ if ret < 0 {
284
+ let errno = unsafe { *libc::__errno_location() };
285
+ if errno == libc::EINTR {
286
+ return 0;
287
+ }
288
+ return -1;
289
+ }
290
+
291
+ if ret == 0 {
292
+ return 0;
293
+ }
294
+
295
+ if pfd.revents & (libc::POLLERR | libc::POLLHUP | libc::POLLNVAL) != 0 {
296
+ return -1;
297
+ }
298
+
299
+ if pfd.revents & libc::POLLIN != 0 {
300
+ return 1;
301
+ }
302
+
303
+ 0
304
+}
src/crates/netipc/src/service/raw/server_session_windows.rs
new
+233
@@ -0,0 +1,233 @@
1
+use super::common::{ensure_client_scratch, SERVER_POLL_TIMEOUT_MS};
2
+use super::dispatch::{
3
+ dispatch_single_internal, method_supported_internal, server_note_payload_capacity,
4
+ DispatchError, DispatchHandler,
5
+};
6
+use crate::protocol::{
7
+ self, batch_item_get, BatchBuilder, Header, FLAG_BATCH, HEADER_SIZE, KIND_REQUEST,
8
+ KIND_RESPONSE, MAGIC_MSG, STATUS_BAD_ENVELOPE, STATUS_INTERNAL_ERROR, STATUS_LIMIT_EXCEEDED,
9
+ STATUS_OK, VERSION,
10
+};
11
+use crate::transport::win_shm::WinShmContext;
12
+use crate::transport::windows::NpSession;
13
+use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
14
+use std::sync::Arc;
15
+
16
+/// Windows: handle one client session over Named Pipe + optional Win SHM.
17
+/// Standalone function for use in per-session threads.
18
+pub(super) fn handle_session_win_threaded(
19
+ mut session: NpSession,
20
+ mut shm: Option<WinShmContext>,
21
+ expected_method_code: u16,
22
+ handler: Option<DispatchHandler>,
23
+ running: Arc<AtomicBool>,
24
+ learned_request_payload_bytes: Arc<AtomicU32>,
25
+ learned_response_payload_bytes: Arc<AtomicU32>,
26
+) {
27
+ let mut recv_buf = vec![0u8; HEADER_SIZE + session.max_request_payload_bytes as usize];
28
+ let mut resp_buf = vec![0u8; session.max_response_payload_bytes as usize];
29
+ let mut item_resp_buf = vec![0u8; session.max_response_payload_bytes as usize];
30
+ let mut msg_buf = vec![0u8; HEADER_SIZE + session.max_response_payload_bytes as usize];
31
+
32
+ while running.load(Ordering::Acquire) {
33
+ let (hdr, payload) = {
34
+ if let Some(ref mut shm_ctx) = shm {
35
+ match shm_ctx.receive(&mut recv_buf, SERVER_POLL_TIMEOUT_MS) {
36
+ Ok(mlen) => {
37
+ if mlen < HEADER_SIZE {
38
+ break;
39
+ }
40
+ let hdr = match Header::decode(&recv_buf[..mlen]) {
41
+ Ok(h) => h,
42
+ Err(_) => break,
43
+ };
44
+ let payload = &recv_buf[HEADER_SIZE..mlen];
45
+ (hdr, payload)
46
+ }
47
+ Err(crate::transport::win_shm::WinShmError::Timeout) => continue,
48
+ Err(_) => break,
49
+ }
50
+ } else {
51
+ match session.wait_readable(SERVER_POLL_TIMEOUT_MS) {
52
+ Ok(true) => {}
53
+ Ok(false) => continue,
54
+ Err(_) => break,
55
+ }
56
+ match session.receive(&mut recv_buf) {
57
+ Ok((hdr, payload)) => (hdr, payload),
58
+ Err(_) => break,
59
+ }
60
+ }
61
+ };
62
+
63
+ if hdr.kind != KIND_REQUEST {
64
+ break;
65
+ }
66
+
67
+ if payload.len() <= u32::MAX as usize {
68
+ server_note_payload_capacity(&learned_request_payload_bytes, payload.len() as u32);
69
+ }
70
+
71
+ if !method_supported_internal(expected_method_code, handler.as_ref(), hdr.code) {
72
+ let mut resp_hdr = Header {
73
+ kind: KIND_RESPONSE,
74
+ code: hdr.code,
75
+ message_id: hdr.message_id,
76
+ transport_status: protocol::STATUS_UNSUPPORTED,
77
+ item_count: 1,
78
+ ..Header::default()
79
+ };
80
+
81
+ if let Some(ref mut shm_ctx) = shm {
82
+ let msg = ensure_client_scratch(&mut msg_buf, HEADER_SIZE);
83
+ resp_hdr.magic = MAGIC_MSG;
84
+ resp_hdr.version = VERSION;
85
+ resp_hdr.header_len = protocol::HEADER_LEN;
86
+ resp_hdr.payload_len = 0;
87
+ resp_hdr.encode(&mut msg[..HEADER_SIZE]);
88
+ if shm_ctx.send(&msg[..HEADER_SIZE]).is_err() {
89
+ break;
90
+ }
91
+ } else if session.send(&mut resp_hdr, &[]).is_err() {
92
+ break;
93
+ }
94
+ continue;
95
+ }
96
+
97
+ let is_batch = (hdr.flags & FLAG_BATCH) != 0 && hdr.item_count >= 1;
98
+ let response_len;
99
+ let dispatch_result = if !is_batch {
100
+ dispatch_single_internal(
101
+ expected_method_code,
102
+ handler.as_ref(),
103
+ hdr.code,
104
+ payload,
105
+ &mut resp_buf,
106
+ )
107
+ } else {
108
+ let mut bb = BatchBuilder::new(&mut resp_buf, hdr.item_count);
109
+ let mut batch_result = Ok(0usize);
110
+
111
+ for i in 0..hdr.item_count {
112
+ let (item_data, _item_len) = match batch_item_get(payload, hdr.item_count, i) {
113
+ Ok(v) => v,
114
+ Err(_) => {
115
+ batch_result = Err(DispatchError::BadEnvelope);
116
+ break;
117
+ }
118
+ };
119
+ let item_len = match dispatch_single_internal(
120
+ expected_method_code,
121
+ handler.as_ref(),
122
+ hdr.code,
123
+ item_data,
124
+ &mut item_resp_buf,
125
+ ) {
126
+ Ok(n) => n,
127
+ Err(err) => {
128
+ batch_result = Err(err);
129
+ break;
130
+ }
131
+ };
132
+ if bb.add(&item_resp_buf[..item_len]).is_err() {
133
+ batch_result = Err(DispatchError::Overflow);
134
+ break;
135
+ }
136
+ }
137
+ if batch_result.is_ok() {
138
+ let (n, _) = bb.finish();
139
+ batch_result = Ok(n);
140
+ }
141
+ batch_result
142
+ };
143
+
144
+ let mut resp_hdr = Header {
145
+ kind: KIND_RESPONSE,
146
+ code: hdr.code,
147
+ message_id: hdr.message_id,
148
+ ..Header::default()
149
+ };
150
+
151
+ match dispatch_result {
152
+ Ok(n) => {
153
+ response_len = n;
154
+ if response_len <= u32::MAX as usize {
155
+ server_note_payload_capacity(
156
+ &learned_response_payload_bytes,
157
+ response_len as u32,
158
+ );
159
+ }
160
+ resp_hdr.transport_status = STATUS_OK;
161
+ if is_batch {
162
+ resp_hdr.flags = FLAG_BATCH;
163
+ resp_hdr.item_count = hdr.item_count;
164
+ } else {
165
+ resp_hdr.flags = 0;
166
+ resp_hdr.item_count = 1;
167
+ }
168
+ }
169
+ Err(DispatchError::Overflow) => {
170
+ let current = session.max_response_payload_bytes;
171
+ if current >= u32::MAX / 2 {
172
+ server_note_payload_capacity(&learned_response_payload_bytes, u32::MAX);
173
+ } else {
174
+ server_note_payload_capacity(&learned_response_payload_bytes, current * 2);
175
+ }
176
+ resp_hdr.transport_status = STATUS_LIMIT_EXCEEDED;
177
+ resp_hdr.item_count = 1;
178
+ resp_hdr.flags = 0;
179
+ response_len = 0;
180
+ }
181
+ Err(DispatchError::BadEnvelope) => {
182
+ resp_hdr.transport_status = STATUS_BAD_ENVELOPE;
183
+ resp_hdr.item_count = 1;
184
+ resp_hdr.flags = 0;
185
+ response_len = 0;
186
+ }
187
+ Err(DispatchError::HandlerFailed) => {
188
+ resp_hdr.transport_status = STATUS_INTERNAL_ERROR;
189
+ resp_hdr.item_count = 1;
190
+ resp_hdr.flags = 0;
191
+ response_len = 0;
192
+ }
193
+ }
194
+
195
+ if let Some(ref mut shm_ctx) = shm {
196
+ let msg_len = HEADER_SIZE + response_len;
197
+ let msg = ensure_client_scratch(&mut msg_buf, msg_len);
198
+
199
+ resp_hdr.magic = MAGIC_MSG;
200
+ resp_hdr.version = VERSION;
201
+ resp_hdr.header_len = protocol::HEADER_LEN;
202
+ resp_hdr.payload_len = response_len as u32;
203
+
204
+ resp_hdr.encode(&mut msg[..HEADER_SIZE]);
205
+ if response_len > 0 {
206
+ msg[HEADER_SIZE..].copy_from_slice(&resp_buf[..response_len]);
207
+ }
208
+
209
+ if shm_ctx.send(msg).is_err() {
210
+ break;
211
+ }
212
+ if resp_hdr.transport_status == STATUS_LIMIT_EXCEEDED {
213
+ break;
214
+ }
215
+ continue;
216
+ }
217
+
218
+ if session
219
+ .send(&mut resp_hdr, &resp_buf[..response_len])
220
+ .is_err()
221
+ {
222
+ break;
223
+ }
224
+ if resp_hdr.transport_status == STATUS_LIMIT_EXCEEDED {
225
+ break;
226
+ }
227
+ }
228
+
229
+ if let Some(mut shm_ctx) = shm {
230
+ shm_ctx.destroy();
231
+ }
232
+ session.close();
233
+}
src/crates/netipc/src/service/raw/server_unix.rs
new
+166
@@ -0,0 +1,166 @@
1
+#![cfg(unix)]
2
+
3
+use super::common::SERVER_POLL_TIMEOUT_MS;
4
+use super::server::{ManagedServer, ServerConfig};
5
+use super::server_session_unix::{handle_session_threaded, poll_fd};
6
+use crate::protocol::NipcError;
7
+#[cfg(target_os = "linux")]
8
+use crate::protocol::{HEADER_SIZE, PROFILE_SHM_FUTEX, PROFILE_SHM_HYBRID};
9
+use crate::transport::posix::UdsListener;
10
+#[cfg(target_os = "linux")]
11
+use crate::transport::shm::ShmContext;
12
+use std::sync::atomic::Ordering;
13
+
14
+impl ManagedServer {
15
+ /// Run the acceptor loop. Blocking. Accepts clients, spawns a
16
+ /// thread per session (up to worker_count concurrent sessions).
17
+ ///
18
+ /// Returns when `stop()` is called or on fatal error.
19
+ #[cfg(unix)]
20
+ pub fn run(&mut self) -> Result<(), NipcError> {
21
+ #[cfg(target_os = "linux")]
22
+ crate::transport::shm::cleanup_stale(&self.run_dir, &self.service_name);
23
+
24
+ let listener = UdsListener::bind(
25
+ &self.run_dir,
26
+ &self.service_name,
27
+ self.server_config.clone(),
28
+ )
29
+ .map_err(|_| NipcError::BadLayout)?;
30
+
31
+ self.running.store(true, Ordering::Release);
32
+
33
+ let mut session_threads: Vec<std::thread::JoinHandle<()>> = Vec::new();
34
+
35
+ while self.running.load(Ordering::Acquire) {
36
+ let ready = poll_fd(listener.fd(), SERVER_POLL_TIMEOUT_MS as i32);
37
+ if ready < 0 {
38
+ break;
39
+ }
40
+ if ready == 0 {
41
+ session_threads.retain(|t| !t.is_finished());
42
+ continue;
43
+ }
44
+
45
+ let (session_id, accept_cfg, precreated_shm, ready) = self.prepare_unix_accept();
46
+ if !ready {
47
+ std::thread::sleep(std::time::Duration::from_millis(10));
48
+ continue;
49
+ }
50
+
51
+ let session = match listener.accept_with_config(session_id, accept_cfg) {
52
+ Ok(s) => s,
53
+ Err(_) => {
54
+ #[cfg(target_os = "linux")]
55
+ if let Some(mut shm) = precreated_shm {
56
+ shm.destroy();
57
+ }
58
+ if !self.running.load(Ordering::Acquire) {
59
+ break;
60
+ }
61
+ std::thread::sleep(std::time::Duration::from_millis(10));
62
+ continue;
63
+ }
64
+ };
65
+
66
+ session_threads.retain(|t| !t.is_finished());
67
+ if session_threads.len() >= self.worker_count {
68
+ #[cfg(target_os = "linux")]
69
+ if let Some(mut shm) = precreated_shm {
70
+ shm.destroy();
71
+ }
72
+ drop(session);
73
+ continue;
74
+ }
75
+
76
+ #[cfg(target_os = "linux")]
77
+ let shm = match self.finalize_unix_shm(&session, precreated_shm) {
78
+ Some(shm) => Some(shm),
79
+ None if session.selected_profile == PROFILE_SHM_HYBRID
80
+ || session.selected_profile == PROFILE_SHM_FUTEX =>
81
+ {
82
+ drop(session);
83
+ continue;
84
+ }
85
+ None => None,
86
+ };
87
+ #[cfg(not(target_os = "linux"))]
88
+ let shm: Option<()> = None;
89
+
90
+ let expected_method_code = self.expected_method_code;
91
+ let handler = self.handler.clone();
92
+ let running = self.running.clone();
93
+ let learned_request_payload_bytes = self.learned_request_payload_bytes.clone();
94
+ let learned_response_payload_bytes = self.learned_response_payload_bytes.clone();
95
+
96
+ let t = std::thread::spawn(move || {
97
+ handle_session_threaded(
98
+ session,
99
+ #[cfg(target_os = "linux")]
100
+ shm,
101
+ #[cfg(not(target_os = "linux"))]
102
+ shm,
103
+ expected_method_code,
104
+ handler,
105
+ running,
106
+ learned_request_payload_bytes,
107
+ learned_response_payload_bytes,
108
+ );
109
+ });
110
+ session_threads.push(t);
111
+ }
112
+
113
+ for t in session_threads {
114
+ let _ = t.join();
115
+ }
116
+
117
+ Ok(())
118
+ }
119
+
120
+ #[cfg(target_os = "linux")]
121
+ fn prepare_unix_accept(&mut self) -> (u64, ServerConfig, Option<ShmContext>, bool) {
122
+ let session_id = self.next_session_id;
123
+ self.next_session_id += 1;
124
+
125
+ let mut cfg = self.server_config.clone();
126
+ cfg.max_request_payload_bytes = self.learned_request_payload_bytes.load(Ordering::Acquire);
127
+ cfg.max_response_payload_bytes =
128
+ self.learned_response_payload_bytes.load(Ordering::Acquire);
129
+
130
+ let shm_profiles = cfg.supported_profiles & (PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
131
+ if shm_profiles == 0 {
132
+ return (session_id, cfg, None, true);
133
+ }
134
+
135
+ match ShmContext::server_create(
136
+ &self.run_dir,
137
+ &self.service_name,
138
+ session_id,
139
+ cfg.max_request_payload_bytes + HEADER_SIZE as u32,
140
+ cfg.max_response_payload_bytes + HEADER_SIZE as u32,
141
+ ) {
142
+ Ok(ctx) => (session_id, cfg, Some(ctx), true),
143
+ Err(_) => {
144
+ cfg.supported_profiles &= !(PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
145
+ cfg.preferred_profiles &= !(PROFILE_SHM_HYBRID | PROFILE_SHM_FUTEX);
146
+ (session_id, cfg.clone(), None, cfg.supported_profiles != 0)
147
+ }
148
+ }
149
+ }
150
+
151
+ #[cfg(target_os = "linux")]
152
+ fn finalize_unix_shm(
153
+ &self,
154
+ session: &crate::transport::posix::UdsSession,
155
+ mut shm: Option<ShmContext>,
156
+ ) -> Option<ShmContext> {
157
+ let profile = session.selected_profile;
158
+ if profile != PROFILE_SHM_HYBRID && profile != PROFILE_SHM_FUTEX {
159
+ if let Some(ref mut ctx) = shm {
160
+ ctx.destroy();
161
+ }
162
+ return None;
163
+ }
164
+ shm
165
+ }
166
+}
src/crates/netipc/src/service/raw/server_windows.rs
new
+215
@@ -0,0 +1,215 @@
1
+use super::server::{ManagedServer, ServerConfig};
2
+use super::server_session_windows::handle_session_win_threaded;
3
+use crate::protocol::{NipcError, HEADER_SIZE};
4
+use crate::transport::win_shm::{
5
+ WinShmContext, PROFILE_BUSYWAIT as WIN_SHM_PROFILE_BUSYWAIT,
6
+ PROFILE_HYBRID as WIN_SHM_PROFILE_HYBRID,
7
+};
8
+use crate::transport::windows::{NpListener, NpSession};
9
+use std::sync::atomic::Ordering;
10
+
11
+impl ManagedServer {
12
+ /// Windows: run the acceptor loop over Named Pipes.
13
+ pub fn run(&mut self) -> Result<(), NipcError> {
14
+ let mut listener = NpListener::bind(
15
+ &self.run_dir,
16
+ &self.service_name,
17
+ self.server_config.clone(),
18
+ )
19
+ .map_err(|_| NipcError::BadLayout)?;
20
+
21
+ self.remember_windows_listener_handle(&listener);
22
+
23
+ self.running.store(true, Ordering::Release);
24
+
25
+ let mut session_threads: Vec<std::thread::JoinHandle<()>> = Vec::new();
26
+
27
+ while self.running.load(Ordering::Acquire) {
28
+ let (session_id, accept_cfg, prepared_shm, ready) = self.prepare_windows_accept();
29
+ if !ready {
30
+ std::thread::sleep(std::time::Duration::from_millis(10));
31
+ continue;
32
+ }
33
+
34
+ self.remember_windows_listener_handle(&listener);
35
+ let accepted = listener.accept_with_config(session_id, accept_cfg);
36
+ self.remember_windows_listener_handle(&listener);
37
+
38
+ let session = match accepted {
39
+ Ok(s) => s,
40
+ Err(_) => {
41
+ if let Some(mut prepared) = prepared_shm {
42
+ prepared.destroy_all();
43
+ }
44
+ if !self.running.load(Ordering::Acquire) {
45
+ break;
46
+ }
47
+ std::thread::sleep(std::time::Duration::from_millis(10));
48
+ continue;
49
+ }
50
+ };
51
+
52
+ session_threads.retain(|t| !t.is_finished());
53
+ if session_threads.len() >= self.worker_count {
54
+ if let Some(mut prepared) = prepared_shm {
55
+ prepared.destroy_all();
56
+ }
57
+ drop(session);
58
+ continue;
59
+ }
60
+
61
+ let shm = self.finalize_windows_shm(&session, prepared_shm);
62
+ if shm.is_none()
63
+ && (session.selected_profile == WIN_SHM_PROFILE_HYBRID
64
+ || session.selected_profile == WIN_SHM_PROFILE_BUSYWAIT)
65
+ {
66
+ drop(session);
67
+ continue;
68
+ }
69
+
70
+ let expected_method_code = self.expected_method_code;
71
+ let handler = self.handler.clone();
72
+ let running = self.running.clone();
73
+ let learned_request_payload_bytes = self.learned_request_payload_bytes.clone();
74
+ let learned_response_payload_bytes = self.learned_response_payload_bytes.clone();
75
+ let t = std::thread::spawn(move || {
76
+ handle_session_win_threaded(
77
+ session,
78
+ shm,
79
+ expected_method_code,
80
+ handler,
81
+ running,
82
+ learned_request_payload_bytes,
83
+ learned_response_payload_bytes,
84
+ );
85
+ });
86
+ session_threads.push(t);
87
+ }
88
+
89
+ *self.listener_handle.lock().unwrap() = None;
90
+
91
+ for t in session_threads {
92
+ let _ = t.join();
93
+ }
94
+
95
+ Ok(())
96
+ }
97
+
98
+ fn remember_windows_listener_handle(&self, listener: &NpListener) {
99
+ let handle = listener.handle();
100
+ let stored = if handle == 0 || handle == -1 {
101
+ None
102
+ } else {
103
+ Some(handle as usize)
104
+ };
105
+ *self.listener_handle.lock().unwrap() = stored;
106
+ }
107
+
108
+ fn prepare_windows_accept(&mut self) -> (u64, ServerConfig, Option<PreparedWinShm>, bool) {
109
+ let session_id = self.next_session_id;
110
+ self.next_session_id += 1;
111
+
112
+ let mut cfg = self.server_config.clone();
113
+ cfg.max_request_payload_bytes = self.learned_request_payload_bytes.load(Ordering::Acquire);
114
+ cfg.max_response_payload_bytes =
115
+ self.learned_response_payload_bytes.load(Ordering::Acquire);
116
+
117
+ let shm_profiles =
118
+ cfg.supported_profiles & (WIN_SHM_PROFILE_HYBRID | WIN_SHM_PROFILE_BUSYWAIT);
119
+ if shm_profiles == 0 {
120
+ return (session_id, cfg, None, true);
121
+ }
122
+
123
+ let mut prepared = PreparedWinShm::default();
124
+ for profile in [WIN_SHM_PROFILE_HYBRID, WIN_SHM_PROFILE_BUSYWAIT] {
125
+ if cfg.supported_profiles & profile == 0 {
126
+ continue;
127
+ }
128
+
129
+ match WinShmContext::server_create(
130
+ &self.run_dir,
131
+ &self.service_name,
132
+ self.server_config.auth_token,
133
+ session_id,
134
+ profile,
135
+ cfg.max_request_payload_bytes + HEADER_SIZE as u32,
136
+ cfg.max_response_payload_bytes + HEADER_SIZE as u32,
137
+ ) {
138
+ Ok(ctx) => prepared.insert(profile, ctx),
139
+ Err(_) => {
140
+ cfg.supported_profiles &= !profile;
141
+ cfg.preferred_profiles &= !profile;
142
+ }
143
+ }
144
+ }
145
+
146
+ if cfg.supported_profiles == 0 {
147
+ prepared.destroy_all();
148
+ return (session_id, cfg, None, false);
149
+ }
150
+
151
+ if prepared.is_empty() {
152
+ return (session_id, cfg, None, true);
153
+ }
154
+
155
+ (session_id, cfg, Some(prepared), true)
156
+ }
157
+
158
+ fn finalize_windows_shm(
159
+ &self,
160
+ session: &NpSession,
161
+ mut prepared: Option<PreparedWinShm>,
162
+ ) -> Option<WinShmContext> {
163
+ let profile = session.selected_profile;
164
+ if profile != WIN_SHM_PROFILE_HYBRID && profile != WIN_SHM_PROFILE_BUSYWAIT {
165
+ if let Some(ref mut prepared) = prepared {
166
+ prepared.destroy_all();
167
+ }
168
+ return None;
169
+ }
170
+ let mut prepared = prepared?;
171
+ // Keep the negotiated context and destroy every unused prepared context.
172
+ let selected = prepared.take(profile);
173
+ prepared.destroy_all();
174
+ selected
175
+ }
176
+}
177
+
178
+#[derive(Default)]
179
+struct PreparedWinShm {
180
+ hybrid: Option<WinShmContext>,
181
+ busywait: Option<WinShmContext>,
182
+}
183
+
184
+impl PreparedWinShm {
185
+ fn insert(&mut self, profile: u32, ctx: WinShmContext) {
186
+ if profile == WIN_SHM_PROFILE_HYBRID {
187
+ self.hybrid = Some(ctx);
188
+ } else if profile == WIN_SHM_PROFILE_BUSYWAIT {
189
+ self.busywait = Some(ctx);
190
+ }
191
+ }
192
+
193
+ fn take(&mut self, profile: u32) -> Option<WinShmContext> {
194
+ if profile == WIN_SHM_PROFILE_HYBRID {
195
+ self.hybrid.take()
196
+ } else if profile == WIN_SHM_PROFILE_BUSYWAIT {
197
+ self.busywait.take()
198
+ } else {
199
+ None
200
+ }
201
+ }
202
+
203
+ fn destroy_all(&mut self) {
204
+ if let Some(mut ctx) = self.hybrid.take() {
205
+ ctx.destroy();
206
+ }
207
+ if let Some(mut ctx) = self.busywait.take() {
208
+ ctx.destroy();
209
+ }
210
+ }
211
+
212
+ fn is_empty(&self) -> bool {
213
+ self.hybrid.is_none() && self.busywait.is_none()
214
+ }
215
+}
src/crates/netipc/src/service/raw/string_reverse.rs
new
+56
@@ -0,0 +1,56 @@
1
+use super::client::{ClientConfig, RawCallKind, RawClient};
2
+use super::dispatch::{DispatchError, DispatchHandler};
3
+use crate::protocol::{
4
+ self, string_reverse_decode, string_reverse_encode, NipcError, METHOD_STRING_REVERSE,
5
+ STRING_REVERSE_HDR_SIZE,
6
+};
7
+use std::sync::Arc;
8
+
9
+pub type StringReverseHandler = Arc<dyn Fn(&str) -> Option<String> + Send + Sync>;
10
+
11
+impl RawClient {
12
+ /// Create a new client context bound to the string-reverse service kind.
13
+ /// Does NOT connect. Does NOT require the server to be running.
14
+ pub fn new_string_reverse(run_dir: &str, service_name: &str, config: ClientConfig) -> Self {
15
+ Self::new_bound(run_dir, service_name, METHOD_STRING_REVERSE, config)
16
+ }
17
+
18
+ /// Blocking typed call: STRING_REVERSE method.
19
+ /// Sends a string, receives the reversed string back.
20
+ ///
21
+ /// The returned view is valid until the next typed call on this client.
22
+ pub fn call_string_reverse(
23
+ &mut self,
24
+ s: &str,
25
+ ) -> Result<protocol::StringReverseView<'_>, NipcError> {
26
+ self.validate_method(METHOD_STRING_REVERSE)?;
27
+ let req_size = STRING_REVERSE_HDR_SIZE
28
+ .checked_add(s.len())
29
+ .and_then(|size| size.checked_add(1))
30
+ .ok_or(NipcError::Overflow)?;
31
+ let req_len = {
32
+ let req_buf = self.request_scratch(req_size);
33
+ let req_len = string_reverse_encode(s.as_bytes(), req_buf);
34
+ if req_len == 0 {
35
+ return Err(NipcError::Truncated);
36
+ }
37
+ req_len
38
+ };
39
+
40
+ let response =
41
+ self.raw_call_with_retry(METHOD_STRING_REVERSE, req_len, RawCallKind::single())?;
42
+ string_reverse_decode(self.response_payload(response)?)
43
+ }
44
+}
45
+
46
+pub fn string_reverse_dispatch(handler: StringReverseHandler) -> DispatchHandler {
47
+ Arc::new(move |request, response_buf| {
48
+ let view = string_reverse_decode(request).map_err(|_| DispatchError::BadEnvelope)?;
49
+ let result = handler(view.as_str()).ok_or(DispatchError::HandlerFailed)?;
50
+ let n = string_reverse_encode(result.as_bytes(), response_buf);
51
+ if n == 0 {
52
+ return Err(DispatchError::Overflow);
53
+ }
54
+ Ok(n)
55
+ })
56
+}
src/crates/netipc/src/service/raw_unix_tests.rs
+198
-1
@@ -1,7 +1,13 @@
1
use super::*;
2
#[cfg(target_os = "linux")]
3
use crate::protocol::PROFILE_SHM_FUTEX;
4
-use crate::protocol::{increment_encode, BatchBuilder, CgroupsBuilder, PROFILE_BASELINE};
4
+use crate::protocol::{
5
+ increment_encode, AppsLookupBuilder, AppsLookupRequestView, BatchBuilder, CgroupsBuilder,
6
+ CgroupsLookupBuilder, CgroupsLookupRequestView, APPS_CGROUP_HOST_ROOT, APPS_CGROUP_KNOWN,
7
+ CGROUP_LOOKUP_KNOWN, CGROUP_LOOKUP_UNKNOWN_RETRY_LATER, METHOD_APPS_LOOKUP,
8
+ METHOD_CGROUPS_LOOKUP, NIPC_UID_UNSET, ORCHESTRATOR_DOCKER, ORCHESTRATOR_K8S, PID_LOOKUP_KNOWN,
9
+ PID_LOOKUP_UNKNOWN, PROFILE_BASELINE,
10
+};
11
use std::os::fd::RawFd;
12
use std::os::unix::ffi::OsStrExt;
13
use std::path::PathBuf;
@@ -131,6 +137,14 @@ fn snapshot_client(service: &str, config: ClientConfig) -> RawClient {
137
RawClient::new_snapshot(TEST_RUN_DIR, service, config)
138
}
139
140
+fn cgroups_lookup_client(service: &str, config: ClientConfig) -> RawClient {
141
+ RawClient::new_cgroups_lookup(TEST_RUN_DIR, service, config)
142
+}
143
+
144
+fn apps_lookup_client(service: &str, config: ClientConfig) -> RawClient {
145
+ RawClient::new_apps_lookup(TEST_RUN_DIR, service, config)
146
+}
147
+
148
fn increment_client(service: &str, config: ClientConfig) -> RawClient {
149
RawClient::new_increment(TEST_RUN_DIR, service, config)
150
}
@@ -251,6 +265,111 @@ fn test_cgroups_dispatch() -> DispatchHandler {
265
snapshot_dispatch(test_cgroups_snapshot_handler(), 3)
266
}
267
268
+fn test_cgroups_lookup_handler() -> CgroupsLookupHandler {
269
+ Arc::new(
270
+ |req: &CgroupsLookupRequestView<'_>, builder: &mut CgroupsLookupBuilder<'_>| {
271
+ for i in 0..req.item_count {
272
+ let item = match req.item(i) {
273
+ Ok(item) => item,
274
+ Err(_) => return false,
275
+ };
276
+ if item.as_bytes() == b"/known" {
277
+ if builder
278
+ .add(
279
+ CGROUP_LOOKUP_KNOWN,
280
+ ORCHESTRATOR_K8S,
281
+ item.as_bytes(),
282
+ b"pod-a",
283
+ &[(b"namespace".as_slice(), b"default".as_slice())],
284
+ )
285
+ .is_err()
286
+ {
287
+ return false;
288
+ }
289
+ } else if builder
290
+ .add(
291
+ CGROUP_LOOKUP_UNKNOWN_RETRY_LATER,
292
+ 0,
293
+ item.as_bytes(),
294
+ b"",
295
+ &[],
296
+ )
297
+ .is_err()
298
+ {
299
+ return false;
300
+ }
301
+ }
302
+ true
303
+ },
304
+ )
305
+}
306
+
307
+fn test_cgroups_lookup_dispatch() -> DispatchHandler {
308
+ cgroups_lookup_dispatch(test_cgroups_lookup_handler())
309
+}
310
+
311
+fn test_apps_lookup_handler() -> AppsLookupHandler {
312
+ Arc::new(
313
+ |req: &AppsLookupRequestView<'_>, builder: &mut AppsLookupBuilder<'_>| {
314
+ for i in 0..req.item_count {
315
+ let pid = match req.item(i) {
316
+ Ok(pid) => pid,
317
+ Err(_) => return false,
318
+ };
319
+ let result = match pid {
320
+ 1234 => builder.add(
321
+ PID_LOOKUP_KNOWN,
322
+ APPS_CGROUP_KNOWN,
323
+ ORCHESTRATOR_DOCKER,
324
+ pid,
325
+ 1,
326
+ 1000,
327
+ 42,
328
+ b"nginx",
329
+ b"/docker/abc",
330
+ b"container-a",
331
+ &[(b"image".as_slice(), b"nginx:latest".as_slice())],
332
+ ),
333
+ 0 => builder.add(
334
+ PID_LOOKUP_KNOWN,
335
+ APPS_CGROUP_HOST_ROOT,
336
+ 0,
337
+ pid,
338
+ 0,
339
+ 0,
340
+ 0,
341
+ b"swapper",
342
+ b"",
343
+ b"",
344
+ &[],
345
+ ),
346
+ _ => builder.add(
347
+ PID_LOOKUP_UNKNOWN,
348
+ APPS_CGROUP_KNOWN,
349
+ 0,
350
+ pid,
351
+ 0,
352
+ NIPC_UID_UNSET,
353
+ 0,
354
+ b"",
355
+ b"",
356
+ b"",
357
+ &[],
358
+ ),
359
+ };
360
+ if result.is_err() {
361
+ return false;
362
+ }
363
+ }
364
+ true
365
+ },
366
+ )
367
+}
368
+
369
+fn test_apps_lookup_dispatch() -> DispatchHandler {
370
+ apps_lookup_dispatch(test_apps_lookup_handler())
371
+}
372
+
373
fn increment_handler() -> IncrementHandler {
374
Arc::new(|value| Some(value + 1))
375
}
@@ -804,6 +923,84 @@ fn test_cgroups_call() {
923
cleanup_all(svc);
924
}
925
926
+#[test]
927
+fn test_cgroups_lookup_call() {
928
+ let svc = "rs_svc_cgroups_lookup";
929
+ ensure_run_dir();
930
+ cleanup_all(svc);
931
+
932
+ let mut server = TestServer::start(
933
+ svc,
934
+ METHOD_CGROUPS_LOOKUP,
935
+ Some(test_cgroups_lookup_dispatch()),
936
+ );
937
+
938
+ let mut client = cgroups_lookup_client(svc, client_config());
939
+ client.refresh();
940
+ assert!(client.ready());
941
+
942
+ let view = client
943
+ .call_cgroups_lookup(&[b"/known".as_slice(), b"/missing".as_slice()])
944
+ .expect("cgroups lookup call");
945
+
946
+ assert_eq!(view.item_count, 2);
947
+ let item0 = view.item(0).expect("item 0");
948
+ assert_eq!(item0.status, CGROUP_LOOKUP_KNOWN);
949
+ assert_eq!(item0.orchestrator, ORCHESTRATOR_K8S);
950
+ assert_eq!(item0.path.as_bytes(), b"/known");
951
+ assert_eq!(item0.name.as_bytes(), b"pod-a");
952
+ assert_eq!(item0.label_count, 1);
953
+
954
+ let item1 = view.item(1).expect("item 1");
955
+ assert_eq!(item1.status, CGROUP_LOOKUP_UNKNOWN_RETRY_LATER);
956
+ assert_eq!(item1.path.as_bytes(), b"/missing");
957
+ assert_eq!(item1.name.as_bytes(), b"");
958
+
959
+ client.close();
960
+ server.stop();
961
+ cleanup_all(svc);
962
+}
963
+
964
+#[test]
965
+fn test_apps_lookup_call() {
966
+ let svc = "rs_svc_apps_lookup";
967
+ ensure_run_dir();
968
+ cleanup_all(svc);
969
+
970
+ let mut server = TestServer::start(svc, METHOD_APPS_LOOKUP, Some(test_apps_lookup_dispatch()));
971
+
972
+ let mut client = apps_lookup_client(svc, client_config());
973
+ client.refresh();
974
+ assert!(client.ready());
975
+
976
+ let view = client
977
+ .call_apps_lookup(&[1234, 0, 9999])
978
+ .expect("apps lookup call");
979
+
980
+ assert_eq!(view.item_count, 3);
981
+ let item0 = view.item(0).expect("item 0");
982
+ assert_eq!(item0.pid, 1234);
983
+ assert_eq!(item0.status, PID_LOOKUP_KNOWN);
984
+ assert_eq!(item0.cgroup_status, APPS_CGROUP_KNOWN);
985
+ assert_eq!(item0.comm.as_bytes(), b"nginx");
986
+ assert_eq!(item0.cgroup_path.as_bytes(), b"/docker/abc");
987
+ assert_eq!(item0.label_count, 1);
988
+
989
+ let item1 = view.item(1).expect("item 1");
990
+ assert_eq!(item1.pid, 0);
991
+ assert_eq!(item1.cgroup_status, APPS_CGROUP_HOST_ROOT);
992
+ assert_eq!(item1.cgroup_path.as_bytes(), b"");
993
+
994
+ let item2 = view.item(2).expect("item 2");
995
+ assert_eq!(item2.pid, 9999);
996
+ assert_eq!(item2.status, PID_LOOKUP_UNKNOWN);
997
+ assert_eq!(item2.uid, NIPC_UID_UNSET);
998
+
999
+ client.close();
1000
+ server.stop();
1001
+ cleanup_all(svc);
1002
+}
1003
+
1004
#[cfg(target_os = "linux")]
1005
#[test]
1006
fn test_cgroups_call_shm() {
src/crates/netipc/src/service/raw_windows_tests.rs
+185
-4
@@ -1,9 +1,13 @@
1
use super::*;
2
use crate::protocol::{
3
- increment_encode, BatchBuilder, CgroupsBuilder, CgroupsRequest, Header, HelloAck, NipcError,
4
- CODE_HELLO_ACK, FLAG_BATCH, HEADER_SIZE, INCREMENT_PAYLOAD_SIZE, KIND_CONTROL, KIND_REQUEST,
5
- KIND_RESPONSE, METHOD_CGROUPS_SNAPSHOT, METHOD_INCREMENT, METHOD_STRING_REVERSE,
6
- PROFILE_BASELINE, PROFILE_SHM_HYBRID, STATUS_INTERNAL_ERROR, STATUS_OK, VERSION,
3
+ increment_encode, AppsLookupBuilder, BatchBuilder, CgroupsBuilder, CgroupsLookupBuilder,
4
+ CgroupsRequest, Header, HelloAck, NipcError, APPS_CGROUP_HOST_ROOT, APPS_CGROUP_KNOWN,
5
+ CGROUP_LOOKUP_KNOWN, CGROUP_LOOKUP_UNKNOWN_RETRY_LATER, CODE_HELLO_ACK, FLAG_BATCH,
6
+ HEADER_SIZE, INCREMENT_PAYLOAD_SIZE, KIND_CONTROL, KIND_REQUEST, KIND_RESPONSE,
7
+ METHOD_APPS_LOOKUP, METHOD_CGROUPS_LOOKUP, METHOD_CGROUPS_SNAPSHOT, METHOD_INCREMENT,
8
+ METHOD_STRING_REVERSE, NIPC_UID_UNSET, ORCHESTRATOR_DOCKER, ORCHESTRATOR_K8S, PID_LOOKUP_KNOWN,
9
+ PID_LOOKUP_UNKNOWN, PROFILE_BASELINE, PROFILE_SHM_HYBRID, STATUS_INTERNAL_ERROR, STATUS_OK,
10
+ VERSION,
11
};
12
use crate::transport::windows::build_pipe_name;
13
use std::ptr;
@@ -116,6 +120,14 @@ fn string_reverse_client(service: &str, config: ClientConfig) -> RawClient {
120
RawClient::new_string_reverse(TEST_RUN_DIR, service, config)
121
}
122
123
+fn cgroups_lookup_client(service: &str, config: ClientConfig) -> RawClient {
124
+ RawClient::new_cgroups_lookup(TEST_RUN_DIR, service, config)
125
+}
126
+
127
+fn apps_lookup_client(service: &str, config: ClientConfig) -> RawClient {
128
+ RawClient::new_apps_lookup(TEST_RUN_DIR, service, config)
129
+}
130
+
131
fn fill_test_cgroups_snapshot(builder: &mut CgroupsBuilder<'_>) -> bool {
132
let items = [
133
(
@@ -161,6 +173,96 @@ fn increment_dispatch_handler() -> DispatchHandler {
173
increment_dispatch(Arc::new(|value| Some(value + 1)))
174
}
175
176
+fn cgroups_lookup_dispatch_handler() -> DispatchHandler {
177
+ cgroups_lookup_dispatch(Arc::new(|req, builder: &mut CgroupsLookupBuilder<'_>| {
178
+ builder.set_generation(55);
179
+ for i in 0..req.item_count {
180
+ let path = match req.item(i) {
181
+ Ok(path) => path,
182
+ Err(_) => return false,
183
+ };
184
+ let result = if path.as_bytes() == b"/docker/abc" {
185
+ builder.add(
186
+ CGROUP_LOOKUP_KNOWN,
187
+ ORCHESTRATOR_DOCKER,
188
+ path.as_bytes(),
189
+ b"container-a",
190
+ &[(b"role".as_slice(), b"web".as_slice())],
191
+ )
192
+ } else {
193
+ builder.add(
194
+ CGROUP_LOOKUP_UNKNOWN_RETRY_LATER,
195
+ 0,
196
+ path.as_bytes(),
197
+ b"",
198
+ &[],
199
+ )
200
+ };
201
+ if result.is_err() {
202
+ return false;
203
+ }
204
+ }
205
+ true
206
+ }))
207
+}
208
+
209
+fn apps_lookup_dispatch_handler() -> DispatchHandler {
210
+ apps_lookup_dispatch(Arc::new(|req, builder: &mut AppsLookupBuilder<'_>| {
211
+ builder.set_generation(77);
212
+ for i in 0..req.item_count {
213
+ let pid = match req.item(i) {
214
+ Ok(pid) => pid,
215
+ Err(_) => return false,
216
+ };
217
+ let result = match pid {
218
+ 123 => builder.add(
219
+ PID_LOOKUP_KNOWN,
220
+ APPS_CGROUP_KNOWN,
221
+ ORCHESTRATOR_K8S,
222
+ pid,
223
+ 1,
224
+ 1000,
225
+ 42,
226
+ b"nginx",
227
+ b"/kubepods/pod-a",
228
+ b"pod-a",
229
+ &[(b"namespace".as_slice(), b"default".as_slice())],
230
+ ),
231
+ 124 => builder.add(
232
+ PID_LOOKUP_KNOWN,
233
+ APPS_CGROUP_HOST_ROOT,
234
+ 0,
235
+ pid,
236
+ 1,
237
+ 0,
238
+ 43,
239
+ b"sshd",
240
+ b"",
241
+ b"",
242
+ &[],
243
+ ),
244
+ _ => builder.add(
245
+ PID_LOOKUP_UNKNOWN,
246
+ APPS_CGROUP_KNOWN,
247
+ 0,
248
+ pid,
249
+ 0,
250
+ NIPC_UID_UNSET,
251
+ 0,
252
+ b"",
253
+ b"",
254
+ b"",
255
+ &[],
256
+ ),
257
+ };
258
+ if result.is_err() {
259
+ return false;
260
+ }
261
+ }
262
+ true
263
+ }))
264
+}
265
+
266
fn connect_ready(client: &mut RawClient) {
267
for _ in 0..200 {
268
client.refresh();
@@ -590,6 +692,85 @@ fn test_cgroups_call_windows_shm() {
692
server.stop();
693
}
694
695
+#[test]
696
+fn test_cgroups_lookup_call_windows_baseline() {
697
+ let svc = unique_service("rs_win_cgroups_lookup");
698
+ let mut server = TestServer::start(
699
+ &svc,
700
+ METHOD_CGROUPS_LOOKUP,
701
+ cgroups_lookup_dispatch_handler(),
702
+ );
703
+
704
+ let mut client = cgroups_lookup_client(&svc, client_config());
705
+ connect_ready(&mut client);
706
+
707
+ let paths: [&[u8]; 2] = [b"/docker/abc".as_slice(), b"/missing".as_slice()];
708
+ let view = client.call_cgroups_lookup(&paths).expect("cgroups lookup");
709
+ assert_eq!(view.item_count, 2);
710
+ assert_eq!(view.generation, 55);
711
+
712
+ let known = view.item(0).expect("known item");
713
+ assert_eq!(known.status, CGROUP_LOOKUP_KNOWN);
714
+ assert_eq!(known.orchestrator, ORCHESTRATOR_DOCKER);
715
+ assert_eq!(known.path.as_bytes(), b"/docker/abc");
716
+ assert_eq!(known.name.as_bytes(), b"container-a");
717
+ let label = known.label(0).expect("known label");
718
+ assert_eq!(label.key.as_bytes(), b"role");
719
+ assert_eq!(label.value.as_bytes(), b"web");
720
+
721
+ let unknown = view.item(1).expect("unknown item");
722
+ assert_eq!(unknown.status, CGROUP_LOOKUP_UNKNOWN_RETRY_LATER);
723
+ assert_eq!(unknown.path.as_bytes(), b"/missing");
724
+ assert_eq!(unknown.name.as_bytes(), b"");
725
+ assert_eq!(client.status().call_count, 1);
726
+
727
+ client.close();
728
+ server.stop();
729
+}
730
+
731
+#[test]
732
+fn test_apps_lookup_call_windows_baseline() {
733
+ let svc = unique_service("rs_win_apps_lookup");
734
+ let mut server = TestServer::start(&svc, METHOD_APPS_LOOKUP, apps_lookup_dispatch_handler());
735
+
736
+ let mut client = apps_lookup_client(&svc, client_config());
737
+ connect_ready(&mut client);
738
+
739
+ let view = client
740
+ .call_apps_lookup(&[123, 124, 999])
741
+ .expect("apps lookup");
742
+ assert_eq!(view.item_count, 3);
743
+ assert_eq!(view.generation, 77);
744
+
745
+ let known = view.item(0).expect("known pid");
746
+ assert_eq!(known.status, PID_LOOKUP_KNOWN);
747
+ assert_eq!(known.cgroup_status, APPS_CGROUP_KNOWN);
748
+ assert_eq!(known.orchestrator, ORCHESTRATOR_K8S);
749
+ assert_eq!(known.pid, 123);
750
+ assert_eq!(known.comm.as_bytes(), b"nginx");
751
+ assert_eq!(known.cgroup_path.as_bytes(), b"/kubepods/pod-a");
752
+ assert_eq!(known.cgroup_name.as_bytes(), b"pod-a");
753
+ let label = known.label(0).expect("known pid label");
754
+ assert_eq!(label.key.as_bytes(), b"namespace");
755
+ assert_eq!(label.value.as_bytes(), b"default");
756
+
757
+ let host = view.item(1).expect("host pid");
758
+ assert_eq!(host.status, PID_LOOKUP_KNOWN);
759
+ assert_eq!(host.cgroup_status, APPS_CGROUP_HOST_ROOT);
760
+ assert_eq!(host.comm.as_bytes(), b"sshd");
761
+ assert_eq!(host.cgroup_path.as_bytes(), b"");
762
+
763
+ let unknown = view.item(2).expect("unknown pid");
764
+ assert_eq!(unknown.status, PID_LOOKUP_UNKNOWN);
765
+ assert_eq!(unknown.pid, 999);
766
+ assert_eq!(unknown.uid, NIPC_UID_UNSET);
767
+ assert_eq!(unknown.comm.as_bytes(), b"");
768
+ assert_eq!(client.status().call_count, 1);
769
+
770
+ client.close();
771
+ server.stop();
772
+}
773
+
774
#[test]
775
fn test_retry_on_failure_windows() {
776
let svc = unique_service("rs_win_svc_retry");
src/crates/netipc/src/transport/posix.rs
+37
-10
@@ -12,6 +12,7 @@ use crate::protocol::{
12
use std::collections::HashSet;
13
use std::ffi::CString;
14
use std::io;
15
+use std::os::unix::fs::MetadataExt;
16
use std::os::unix::io::RawFd;
17
use std::path::{Path, PathBuf};
18
use std::sync::atomic::{AtomicU64, Ordering};
@@ -292,7 +293,9 @@ impl UdsSession {
293
self.max_response_batch_items,
294
)
295
};
295
- if payload.len() > max_payload as usize || payload.len() > u32::MAX as usize {
296
+ if payload.len() > max_payload as usize
297
+ || payload.len() > (u32::MAX as usize).saturating_sub(HEADER_SIZE)
298
+ {
299
return Err(UdsError::LimitExceeded);
300
}
301
if hdr.item_count > max_items {
@@ -352,7 +355,7 @@ impl UdsSession {
355
let remaining_after_first = payload.len() - first_chunk_payload;
356
357
let continuation_chunks = if remaining_after_first > 0 {
355
- (remaining_after_first + chunk_payload_budget - 1) / chunk_payload_budget
358
+ 1 + ((remaining_after_first - 1) / chunk_payload_budget)
359
} else {
360
0
361
};
@@ -445,8 +448,14 @@ impl UdsSession {
448
449
let total_msg = HEADER_SIZE + hdr.payload_len as usize;
450
451
+ if n > total_msg {
452
+ return Err(UdsError::Protocol(
453
+ "packet exceeds declared payload_len".into(),
454
+ ));
455
+ }
456
+
457
// Non-chunked: entire message in one packet
449
- if n >= total_msg {
458
+ if n == total_msg {
459
let payload = &buf[HEADER_SIZE..HEADER_SIZE + hdr.payload_len as usize];
460
461
// Validate batch directory
@@ -487,7 +496,7 @@ impl UdsSession {
496
// Expected chunk count
497
let remaining_after_first = hdr.payload_len as usize - first_payload_bytes;
498
let expected_continuations = if remaining_after_first > 0 && chunk_payload_budget > 0 {
490
- (remaining_after_first + chunk_payload_budget - 1) / chunk_payload_budget
499
+ 1 + ((remaining_after_first - 1) / chunk_payload_budget)
500
} else {
501
0
502
};
@@ -593,7 +602,7 @@ impl UdsListener {
602
let path = build_socket_path(run_dir, service_name)?;
603
604
// Stale recovery
596
- match check_and_recover_stale(&path) {
605
+ match check_and_recover_stale(&path, run_dir_allows_stale_unlink(run_dir)) {
606
StaleResult::LiveServer => return Err(UdsError::AddrInUse),
607
StaleResult::Stale | StaleResult::NotExist => { /* proceed */ }
608
}
@@ -910,7 +919,17 @@ enum StaleResult {
919
LiveServer,
920
}
921
913
-fn check_and_recover_stale(path: &str) -> StaleResult {
922
+fn run_dir_allows_stale_unlink(run_dir: &str) -> bool {
923
+ let metadata = match std::fs::metadata(run_dir) {
924
+ Ok(m) => m,
925
+ Err(_) => return false,
926
+ };
927
+ metadata.is_dir()
928
+ && metadata.uid() == unsafe { libc::geteuid() }
929
+ && metadata.mode() & 0o022 == 0
930
+}
931
+
932
+fn check_and_recover_stale(path: &str, allow_stale_unlink: bool) -> StaleResult {
933
if !Path::new(path).exists() {
934
return StaleResult::NotExist;
935
}
@@ -926,10 +945,18 @@ fn check_and_recover_stale(path: &str) -> StaleResult {
945
// Connected => live server
946
StaleResult::LiveServer
947
}
929
- Err(UdsError::Connect(e)) if e == libc::ECONNREFUSED || e == libc::ENOENT => {
930
- // Connection refused or no such socket => stale, unlink
931
- let _ = std::fs::remove_file(path);
932
- StaleResult::Stale
948
+ Err(UdsError::Connect(e)) if e == libc::ENOENT => StaleResult::NotExist,
949
+ Err(UdsError::Connect(e)) if e == libc::ECONNREFUSED => {
950
+ if !allow_stale_unlink {
951
+ StaleResult::LiveServer
952
+ } else {
953
+ // Connection refused means stale; unlink only in a private run dir.
954
+ match std::fs::remove_file(path) {
955
+ Ok(()) => StaleResult::Stale,
956
+ Err(err) if err.kind() == io::ErrorKind::NotFound => StaleResult::NotExist,
957
+ Err(_) => StaleResult::LiveServer,
958
+ }
959
+ }
960
}
961
Err(_) => {
962
// Other errors (EACCES, etc.) — can't determine ownership,
src/crates/netipc/src/transport/posix_tests.rs
+34
@@ -1461,6 +1461,40 @@ fn test_receive_packet_too_short_for_header() {
1461
unsafe { libc::close(fd1) };
1462
}
1463
1464
+#[test]
1465
+fn test_receive_packet_longer_than_declared_payload() {
1466
+ let (fd0, fd1) = socketpair_seqpacket();
1467
+ let mut session = test_session(fd0, Role::Server, 4096);
1468
+
1469
+ let payload = [0xBE, 0xEF];
1470
+ let mut pkt = [0u8; HEADER_SIZE + 2];
1471
+ let hdr = Header {
1472
+ magic: MAGIC_MSG,
1473
+ version: VERSION,
1474
+ header_len: protocol::HEADER_LEN,
1475
+ kind: KIND_REQUEST,
1476
+ code: 1,
1477
+ flags: 0,
1478
+ transport_status: protocol::STATUS_OK,
1479
+ payload_len: 1,
1480
+ item_count: 1,
1481
+ message_id: 1,
1482
+ };
1483
+ hdr.encode(&mut pkt[..HEADER_SIZE]);
1484
+ pkt[HEADER_SIZE..].copy_from_slice(&payload);
1485
+
1486
+ raw_send(fd1, &pkt).expect("send packet with trailing bytes");
1487
+
1488
+ let mut buf = [0u8; 128];
1489
+ let err = session
1490
+ .receive(&mut buf)
1491
+ .expect_err("trailing bytes should be rejected");
1492
+ assert!(matches!(err, UdsError::Protocol(ref msg)
1493
+ if msg.contains("exceeds declared payload_len")));
1494
+
1495
+ unsafe { libc::close(fd1) };
1496
+}
1497
+
1498
#[test]
1499
fn test_receive_batch_directory_too_short_nonchunked() {
1500
let (fd0, fd1) = socketpair_seqpacket();
src/crates/netipc/src/transport/shm.rs
+36
-11
@@ -6,6 +6,8 @@
6
//!
7
//! Wire-compatible with the C implementation in netipc_shm.c.
8
9
+use std::ffi::CString;
10
+use std::os::unix::fs::MetadataExt;
11
use std::path::{Path, PathBuf};
12
use std::ptr;
13
@@ -245,7 +247,7 @@ impl ShmContext {
247
248
// If O_EXCL failed (file exists), do stale recovery and retry.
249
if fd < 0 && unsafe { *libc::__errno_location() } == libc::EEXIST {
248
- let stale = check_shm_stale(&path);
250
+ let stale = check_shm_stale(&path, run_dir_allows_stale_unlink(run_dir));
251
if stale == StaleResult::LiveServer {
252
return Err(ShmError::AddrInUse);
253
}
@@ -740,6 +742,7 @@ pub fn cleanup_stale(run_dir: &str, service_name: &str) {
742
Ok(e) => e,
743
Err(_) => return,
744
};
745
+ let allow_stale_unlink = run_dir_allows_stale_unlink(run_dir);
746
747
for entry in entries.flatten() {
748
let name = match entry.file_name().into_string() {
@@ -764,7 +767,7 @@ pub fn cleanup_stale(run_dir: &str, service_name: &str) {
767
// target disappeared) — remove the stale directory entry. Any other
768
// open failure is ambiguous, so leave the entry alone.
769
if should_unlink_cleanup_open_failure(errno()) {
767
- unsafe { libc::unlink(c_path.as_ptr()) };
770
+ let _ = unlink_stale_path(&c_path, allow_stale_unlink);
771
}
772
continue;
773
}
@@ -773,8 +776,8 @@ pub fn cleanup_stale(run_dir: &str, service_name: &str) {
776
if unsafe { libc::fstat(fd, &mut st) } != 0 || (st.st_size as usize) < HEADER_LEN as usize {
777
unsafe {
778
libc::close(fd);
776
- libc::unlink(c_path.as_ptr());
779
}
780
+ let _ = unlink_stale_path(&c_path, allow_stale_unlink);
781
continue;
782
}
783
@@ -791,7 +794,7 @@ pub fn cleanup_stale(run_dir: &str, service_name: &str) {
794
unsafe { libc::close(fd) };
795
796
if map == libc::MAP_FAILED {
794
- unsafe { libc::unlink(c_path.as_ptr()) };
797
+ let _ = unlink_stale_path(&c_path, allow_stale_unlink);
798
continue;
799
}
800
@@ -800,8 +803,8 @@ pub fn cleanup_stale(run_dir: &str, service_name: &str) {
803
if magic != REGION_MAGIC {
804
unsafe {
805
libc::munmap(map, HEADER_LEN as usize);
803
- libc::unlink(c_path.as_ptr());
806
}
807
+ let _ = unlink_stale_path(&c_path, allow_stale_unlink);
808
continue;
809
}
810
@@ -811,7 +814,7 @@ pub fn cleanup_stale(run_dir: &str, service_name: &str) {
814
815
// If owner is dead (or generation is zero / legacy), unlink
816
if !pid_alive(owner) || gen == 0 {
814
- unsafe { libc::unlink(c_path.as_ptr()) };
817
+ let _ = unlink_stale_path(&c_path, allow_stale_unlink);
818
}
819
}
820
}
@@ -970,6 +973,20 @@ fn should_unlink_cleanup_open_failure(err: i32) -> bool {
973
err == libc::ENOENT
974
}
975
976
+fn run_dir_allows_stale_unlink(run_dir: &str) -> bool {
977
+ let metadata = match std::fs::metadata(run_dir) {
978
+ Ok(m) => m,
979
+ Err(_) => return false,
980
+ };
981
+ metadata.is_dir()
982
+ && metadata.uid() == unsafe { libc::geteuid() }
983
+ && metadata.mode() & 0o022 == 0
984
+}
985
+
986
+fn unlink_stale_path(c_path: &CString, allow_stale_unlink: bool) -> bool {
987
+ allow_stale_unlink && (unsafe { libc::unlink(c_path.as_ptr()) } == 0 || errno() == libc::ENOENT)
988
+}
989
+
990
fn classify_stale_open_failure(err: i32) -> StaleResult {
991
if err == libc::ENOENT {
992
StaleResult::NotExist
@@ -979,7 +996,7 @@ fn classify_stale_open_failure(err: i32) -> StaleResult {
996
}
997
998
#[allow(dead_code)]
982
-fn check_shm_stale(path: &Path) -> StaleResult {
999
+fn check_shm_stale(path: &Path, allow_stale_unlink: bool) -> StaleResult {
1000
let c_path = match path_to_cstring(path) {
1001
Ok(c) => c,
1002
Err(_) => return StaleResult::NotExist,
@@ -991,7 +1008,9 @@ fn check_shm_stale(path: &Path) -> StaleResult {
1008
}
1009
1010
if (st.st_size as usize) < HEADER_LEN as usize {
994
- unsafe { libc::unlink(c_path.as_ptr()) };
1011
+ if !unlink_stale_path(&c_path, allow_stale_unlink) {
1012
+ return StaleResult::LiveServer;
1013
+ }
1014
return StaleResult::Invalid;
1015
}
1016
@@ -1013,7 +1032,9 @@ fn check_shm_stale(path: &Path) -> StaleResult {
1032
unsafe { libc::close(fd) };
1033
1034
if map == libc::MAP_FAILED {
1016
- unsafe { libc::unlink(c_path.as_ptr()) };
1035
+ if !unlink_stale_path(&c_path, allow_stale_unlink) {
1036
+ return StaleResult::LiveServer;
1037
+ }
1038
return StaleResult::Invalid;
1039
}
1040
@@ -1022,7 +1043,9 @@ fn check_shm_stale(path: &Path) -> StaleResult {
1043
if magic != REGION_MAGIC {
1044
unsafe {
1045
libc::munmap(map, HEADER_LEN as usize);
1025
- libc::unlink(c_path.as_ptr());
1046
+ }
1047
+ if !unlink_stale_path(&c_path, allow_stale_unlink) {
1048
+ return StaleResult::LiveServer;
1049
}
1050
return StaleResult::Invalid;
1051
}
@@ -1036,7 +1059,9 @@ fn check_shm_stale(path: &Path) -> StaleResult {
1059
}
1060
1061
// Dead owner or zero generation (PID reuse / legacy) — stale
1039
- unsafe { libc::unlink(c_path.as_ptr()) };
1062
+ if !unlink_stale_path(&c_path, allow_stale_unlink) {
1063
+ return StaleResult::LiveServer;
1064
+ }
1065
StaleResult::Recovered
1066
}
1067
src/crates/netipc/src/transport/shm_tests.rs
+16
-7
@@ -1125,7 +1125,10 @@ fn test_check_shm_stale_nonexistent_returns_not_exist() {
1125
cleanup_shm(svc, sid);
1126
1127
let path = build_shm_path(TEST_RUN_DIR, svc, sid).expect("path");
1128
- assert!(matches!(check_shm_stale(&path), StaleResult::NotExist));
1128
+ assert!(matches!(
1129
+ check_shm_stale(&path, true),
1130
+ StaleResult::NotExist
1131
+ ));
1132
}
1133
1134
#[test]
@@ -1133,7 +1136,10 @@ fn test_check_shm_stale_invalid_cstring_returns_not_exist() {
1136
let bad_path = PathBuf::from(OsString::from_vec(vec![
1137
b'/', b't', b'm', b'p', b'/', b'n', b'i', b'p', b'c', 0, b'b',
1138
]));
1136
- assert!(matches!(check_shm_stale(&bad_path), StaleResult::NotExist));
1139
+ assert!(matches!(
1140
+ check_shm_stale(&bad_path, true),
1141
+ StaleResult::NotExist
1142
+ ));
1143
}
1144
1145
#[test]
@@ -1259,7 +1265,7 @@ fn test_check_shm_stale_short_file_invalid() {
1265
let path = build_shm_path(TEST_RUN_DIR, svc, sid).expect("path");
1266
std::fs::write(&path, [0u8; 8]).expect("write short file");
1267
1262
- assert!(matches!(check_shm_stale(&path), StaleResult::Invalid));
1268
+ assert!(matches!(check_shm_stale(&path, true), StaleResult::Invalid));
1269
assert!(!path.exists(), "short stale file should be removed");
1270
}
1271
@@ -1277,7 +1283,7 @@ fn test_check_shm_stale_bad_magic_invalid() {
1283
server.close();
1284
1285
let path = build_shm_path(TEST_RUN_DIR, svc, sid).expect("path");
1280
- assert!(matches!(check_shm_stale(&path), StaleResult::Invalid));
1286
+ assert!(matches!(check_shm_stale(&path, true), StaleResult::Invalid));
1287
assert!(!path.exists(), "bad magic stale file should be removed");
1288
}
1289
@@ -1295,7 +1301,10 @@ fn test_check_shm_stale_zero_generation_recovers() {
1301
server.close();
1302
1303
let path = build_shm_path(TEST_RUN_DIR, svc, sid).expect("path");
1298
- assert!(matches!(check_shm_stale(&path), StaleResult::Recovered));
1304
+ assert!(matches!(
1305
+ check_shm_stale(&path, true),
1306
+ StaleResult::Recovered
1307
+ ));
1308
assert!(
1309
!path.exists(),
1310
"zero-generation stale file should be removed"
@@ -1333,7 +1342,7 @@ fn test_check_shm_stale_open_failure_invalid() {
1342
errno()
1343
);
1344
1336
- assert!(matches!(check_shm_stale(&path), StaleResult::Invalid));
1345
+ assert!(matches!(check_shm_stale(&path, true), StaleResult::Invalid));
1346
// Under non-root: file preserved (EACCES). Under root: chmod 000
1347
// has no effect, so the file is opened, inspected, and removed.
1348
if unsafe { libc::geteuid() } != 0 {
@@ -1359,7 +1368,7 @@ fn test_check_shm_stale_directory_symlink_invalid() {
1368
std::fs::create_dir_all(&target).expect("create target dir");
1369
std::os::unix::fs::symlink(&target, &path).expect("create symlink");
1370
1362
- assert!(matches!(check_shm_stale(&path), StaleResult::Invalid));
1371
+ assert!(matches!(check_shm_stale(&path, true), StaleResult::Invalid));
1372
assert!(
1373
!path.exists(),
1374
"directory symlink stale entry should be removed"
src/crates/netipc/src/transport/windows.rs
+66
-4
@@ -689,7 +689,9 @@ impl NpSession {
689
self.max_response_batch_items,
690
)
691
};
692
- if payload.len() > max_payload as usize || payload.len() > u32::MAX as usize {
692
+ if payload.len() > max_payload as usize
693
+ || payload.len() > (u32::MAX as usize).saturating_sub(HEADER_SIZE)
694
+ {
695
return Err(NpError::LimitExceeded);
696
}
697
if hdr.item_count > max_items {
@@ -748,7 +750,7 @@ impl NpSession {
750
let remaining_after_first = payload.len() - first_chunk_payload;
751
752
let continuation_chunks = if remaining_after_first > 0 {
751
- (remaining_after_first + chunk_payload_budget - 1) / chunk_payload_budget
753
+ 1 + ((remaining_after_first - 1) / chunk_payload_budget)
754
} else {
755
0
756
};
@@ -847,8 +849,14 @@ impl NpSession {
849
850
let total_msg = HEADER_SIZE + hdr.payload_len as usize;
851
852
+ if n > total_msg {
853
+ return Err(NpError::Protocol(
854
+ "packet exceeds declared payload_len".into(),
855
+ ));
856
+ }
857
+
858
// Non-chunked
851
- if n >= total_msg {
859
+ if n == total_msg {
860
let payload = &buf[HEADER_SIZE..HEADER_SIZE + hdr.payload_len as usize];
861
862
// Validate batch directory
@@ -885,7 +893,7 @@ impl NpSession {
893
894
let remaining_after_first = hdr.payload_len as usize - first_payload_bytes;
895
let expected_continuations = if remaining_after_first > 0 && chunk_payload_budget > 0 {
888
- (remaining_after_first + chunk_payload_budget - 1) / chunk_payload_budget
896
+ 1 + ((remaining_after_first - 1) / chunk_payload_budget)
897
} else {
898
0
899
};
@@ -1792,6 +1800,60 @@ mod tests {
1800
server.join().expect("server join");
1801
}
1802
1803
+ #[cfg(windows)]
1804
+ #[test]
1805
+ fn test_receive_packet_longer_than_declared_payload() {
1806
+ ensure_run_dir();
1807
+ let svc = unique_service("rs_trailing");
1808
+
1809
+ let mut listener =
1810
+ NpListener::bind(TEST_RUN_DIR, &svc, default_server_config()).expect("bind");
1811
+ let server = thread::spawn(move || {
1812
+ let mut session = listener.accept().expect("accept");
1813
+ let mut buf = [0u8; 256];
1814
+ let (req_hdr, _) = session.receive(&mut buf).expect("recv request");
1815
+
1816
+ let mut pkt = [0u8; HEADER_SIZE + 2];
1817
+ let resp_hdr = Header {
1818
+ magic: MAGIC_MSG,
1819
+ version: VERSION,
1820
+ header_len: protocol::HEADER_LEN,
1821
+ kind: KIND_RESPONSE,
1822
+ code: req_hdr.code,
1823
+ flags: 0,
1824
+ transport_status: protocol::STATUS_OK,
1825
+ payload_len: 1,
1826
+ item_count: 1,
1827
+ message_id: req_hdr.message_id,
1828
+ };
1829
+ resp_hdr.encode(&mut pkt[..HEADER_SIZE]);
1830
+ pkt[HEADER_SIZE..].copy_from_slice(&[0xBE, 0xEF]);
1831
+ raw_write(session.handle, &pkt).expect("raw trailing packet write");
1832
+ session.close();
1833
+ });
1834
+
1835
+ let mut session =
1836
+ NpSession::connect(TEST_RUN_DIR, &svc, &default_client_config()).expect("connect");
1837
+ let mut hdr = Header {
1838
+ kind: KIND_REQUEST,
1839
+ code: protocol::METHOD_INCREMENT,
1840
+ item_count: 1,
1841
+ message_id: 43,
1842
+ ..Header::default()
1843
+ };
1844
+ session.send(&mut hdr, &[0xAA]).expect("send request");
1845
+
1846
+ let mut rbuf = [0u8; 256];
1847
+ let err = session
1848
+ .receive(&mut rbuf)
1849
+ .expect_err("trailing bytes should be rejected");
1850
+ assert!(matches!(err, NpError::Protocol(ref msg)
1851
+ if msg.contains("exceeds declared payload_len")));
1852
+
1853
+ session.close();
1854
+ server.join().expect("server join");
1855
+ }
1856
+
1857
#[cfg(windows)]
1858
#[test]
1859
fn test_chunking_and_received_payload() {
src/go/pkg/netipc/protocol/apps_lookup.go
new
+691
@@ -0,0 +1,691 @@
1
+package protocol
2
+
3
+const (
4
+ NipcUIDUnset uint32 = ^uint32(0)
5
+
6
+ PidLookupKnown uint16 = 0
7
+ PidLookupUnknown uint16 = 1
8
+
9
+ AppsCgroupKnown uint16 = 0
10
+ AppsCgroupUnknownRetryLater uint16 = 1
11
+ AppsCgroupUnknownPermanent uint16 = 2
12
+ AppsCgroupHostRoot uint16 = 3
13
+
14
+ AppsLookupReqHdr = 16
15
+ AppsLookupRespHdr = 16
16
+ AppsLookupItemHdr = 60
17
+ AppsLookupKeySize = 8
18
+
19
+ appsLookupUnknownItemSize = AppsLookupItemHdr + 3
20
+)
21
+
22
+type AppsLookupRequestView struct {
23
+ ItemCount uint32
24
+ payload []byte
25
+}
26
+
27
+type AppsLookupResponseView struct {
28
+ LayoutVersion uint16
29
+ Flags uint16
30
+ ItemCount uint32
31
+ Generation uint64
32
+ payload []byte
33
+}
34
+
35
+type AppsLookupItemView struct {
36
+ Status uint16
37
+ Orchestrator uint16
38
+ CgroupStatus uint16
39
+ Pid uint32
40
+ Ppid uint32
41
+ Uid uint32
42
+ Starttime uint64
43
+ Comm CStringView
44
+ CgroupPath CStringView
45
+ CgroupName CStringView
46
+ LabelCount uint16
47
+ item []byte
48
+ labelTableOffset int
49
+}
50
+
51
+type appsLookupSemantics struct {
52
+ status uint16
53
+ cgroupStatus uint16
54
+ orchestrator uint16
55
+ ppid uint32
56
+ uid uint32
57
+ starttime uint64
58
+ commLen int
59
+ pathLen int
60
+ nameLen int
61
+ labelCount int
62
+}
63
+
64
+func validateAppsLookupSemantics(v appsLookupSemantics) error {
65
+ if err := validateAppsLookupDomains(v.status, v.cgroupStatus, v.commLen); err != nil {
66
+ return err
67
+ }
68
+ if v.status == PidLookupUnknown {
69
+ return validateAppsLookupUnknown(v)
70
+ }
71
+ return validateAppsLookupKnown(v)
72
+}
73
+
74
+func validateAppsLookupDomains(status, cgroupStatus uint16, commLen int) error {
75
+ if status != PidLookupKnown && status != PidLookupUnknown {
76
+ return ErrBadLayout
77
+ }
78
+ if cgroupStatus != AppsCgroupKnown && cgroupStatus != AppsCgroupUnknownRetryLater &&
79
+ cgroupStatus != AppsCgroupUnknownPermanent && cgroupStatus != AppsCgroupHostRoot {
80
+ return ErrBadLayout
81
+ }
82
+ if commLen > 15 {
83
+ return ErrBadLayout
84
+ }
85
+ return nil
86
+}
87
+
88
+func validateAppsLookupUnknown(v appsLookupSemantics) error {
89
+ if v.orchestrator != 0 || v.cgroupStatus != 0 || v.ppid != 0 || v.uid != NipcUIDUnset ||
90
+ v.starttime != 0 || v.commLen != 0 || v.pathLen != 0 || v.nameLen != 0 || v.labelCount != 0 {
91
+ return ErrBadLayout
92
+ }
93
+ return nil
94
+}
95
+
96
+func validateAppsLookupKnown(v appsLookupSemantics) error {
97
+ if v.commLen == 0 {
98
+ return ErrBadLayout
99
+ }
100
+ switch v.cgroupStatus {
101
+ case AppsCgroupKnown:
102
+ if v.pathLen == 0 {
103
+ return ErrBadLayout
104
+ }
105
+ case AppsCgroupUnknownRetryLater:
106
+ if v.orchestrator != 0 || v.nameLen != 0 || v.labelCount != 0 {
107
+ return ErrBadLayout
108
+ }
109
+ case AppsCgroupUnknownPermanent:
110
+ if v.pathLen == 0 || v.orchestrator != 0 || v.nameLen != 0 || v.labelCount != 0 {
111
+ return ErrBadLayout
112
+ }
113
+ case AppsCgroupHostRoot:
114
+ if v.orchestrator != 0 || v.pathLen != 0 || v.nameLen != 0 || v.labelCount != 0 {
115
+ return ErrBadLayout
116
+ }
117
+ }
118
+ return nil
119
+}
120
+
121
+func EncodeAppsLookupRequest(pids []uint32, buf []byte) (int, error) {
122
+ count := len(pids)
123
+ if uint64(count) > uint64(^uint32(0)) {
124
+ return 0, ErrOverflow
125
+ }
126
+ dirSize, ok := checkedMulInt(count, LookupDirEntrySize)
127
+ if !ok {
128
+ return 0, ErrOverflow
129
+ }
130
+ keySize, ok := checkedMulInt(count, AppsLookupKeySize)
131
+ if !ok {
132
+ return 0, ErrOverflow
133
+ }
134
+ packedStart, ok := checkedAddInt(AppsLookupReqHdr, dirSize)
135
+ if !ok {
136
+ return 0, ErrOverflow
137
+ }
138
+ total, ok := checkedAddInt(packedStart, keySize)
139
+ if !ok {
140
+ return 0, ErrOverflow
141
+ }
142
+ if total > len(buf) {
143
+ return 0, ErrOverflow
144
+ }
145
+ for i, pid := range pids {
146
+ offset32, ok := checkedU32Int(i * AppsLookupKeySize)
147
+ if !ok {
148
+ return 0, ErrOverflow
149
+ }
150
+ base := AppsLookupReqHdr + i*LookupDirEntrySize
151
+ ne.PutUint32(buf[base:base+4], offset32)
152
+ ne.PutUint32(buf[base+4:base+8], AppsLookupKeySize)
153
+ key := packedStart + i*AppsLookupKeySize
154
+ ne.PutUint32(buf[key:key+4], pid)
155
+ ne.PutUint32(buf[key+4:key+8], 0)
156
+ }
157
+ ne.PutUint16(buf[0:2], 1)
158
+ ne.PutUint16(buf[2:4], 0)
159
+ ne.PutUint32(buf[4:8], uint32(count))
160
+ ne.PutUint32(buf[8:12], 0)
161
+ ne.PutUint32(buf[12:16], 0)
162
+ return total, nil
163
+}
164
+
165
+func DecodeAppsLookupRequest(buf []byte) (*AppsLookupRequestView, error) {
166
+ if len(buf) < AppsLookupReqHdr {
167
+ return nil, ErrTruncated
168
+ }
169
+ if ne.Uint16(buf[0:2]) != 1 || ne.Uint16(buf[2:4]) != 0 ||
170
+ ne.Uint32(buf[8:12]) != 0 || ne.Uint32(buf[12:16]) != 0 {
171
+ return nil, ErrBadLayout
172
+ }
173
+ itemCount := ne.Uint32(buf[4:8])
174
+ dirSize64 := uint64(itemCount) * uint64(LookupDirEntrySize)
175
+ dirEnd, ok := checkedInt(uint64(AppsLookupReqHdr) + dirSize64)
176
+ if !ok {
177
+ return nil, ErrBadItemCount
178
+ }
179
+ if dirEnd > len(buf) {
180
+ return nil, ErrTruncated
181
+ }
182
+ if err := validateLookupDir(buf, AppsLookupReqHdr, itemCount, len(buf)-dirEnd, 0, AppsLookupKeySize); err != nil {
183
+ return nil, err
184
+ }
185
+ for i := range itemCount {
186
+ base := AppsLookupReqHdr + int(i)*LookupDirEntrySize
187
+ off, _, err := lookupDirEntry(buf, base)
188
+ if err != nil {
189
+ return nil, err
190
+ }
191
+ key, err := lookupPayloadSlice(buf, dirEnd, off, AppsLookupKeySize)
192
+ if err != nil {
193
+ return nil, err
194
+ }
195
+ if ne.Uint32(key[4:8]) != 0 {
196
+ return nil, ErrBadLayout
197
+ }
198
+ }
199
+ return &AppsLookupRequestView{ItemCount: itemCount, payload: buf}, nil
200
+}
201
+
202
+func (v *AppsLookupRequestView) Item(index uint32) (uint32, error) {
203
+ if index >= v.ItemCount {
204
+ return 0, ErrOutOfBounds
205
+ }
206
+ dirEnd, ok := lookupBuilderDataOffset(AppsLookupReqHdr, v.ItemCount)
207
+ if !ok {
208
+ return 0, ErrOverflow
209
+ }
210
+ base, ok := lookupDirOffset(AppsLookupReqHdr, index)
211
+ if !ok {
212
+ return 0, ErrOverflow
213
+ }
214
+ off, _, err := lookupDirEntry(v.payload, base)
215
+ if err != nil {
216
+ return 0, err
217
+ }
218
+ key, err := lookupPayloadSlice(v.payload, dirEnd, off, AppsLookupKeySize)
219
+ if err != nil {
220
+ return 0, err
221
+ }
222
+ return ne.Uint32(key[0:4]), nil
223
+}
224
+
225
+func DecodeAppsLookupResponse(buf []byte) (*AppsLookupResponseView, error) {
226
+ if len(buf) < AppsLookupRespHdr {
227
+ return nil, ErrTruncated
228
+ }
229
+ if ne.Uint16(buf[0:2]) != 1 || ne.Uint16(buf[2:4]) != 0 {
230
+ return nil, ErrBadLayout
231
+ }
232
+ itemCount := ne.Uint32(buf[4:8])
233
+ dirEnd, ok := checkedInt(uint64(AppsLookupRespHdr) + uint64(itemCount)*uint64(LookupDirEntrySize))
234
+ if !ok {
235
+ return nil, ErrBadItemCount
236
+ }
237
+ if dirEnd > len(buf) {
238
+ return nil, ErrTruncated
239
+ }
240
+ if err := validateLookupDir(buf, AppsLookupRespHdr, itemCount, len(buf)-dirEnd, AppsLookupItemHdr, -1); err != nil {
241
+ return nil, err
242
+ }
243
+ for i := range itemCount {
244
+ base := AppsLookupRespHdr + int(i)*LookupDirEntrySize
245
+ off, length, err := lookupDirEntry(buf, base)
246
+ if err != nil {
247
+ return nil, err
248
+ }
249
+ item, err := lookupPayloadSlice(buf, dirEnd, off, length)
250
+ if err != nil {
251
+ return nil, err
252
+ }
253
+ if _, err := decodeAppsLookupItem(item); err != nil {
254
+ return nil, err
255
+ }
256
+ }
257
+ return &AppsLookupResponseView{
258
+ LayoutVersion: 1,
259
+ Flags: 0,
260
+ ItemCount: itemCount,
261
+ Generation: ne.Uint64(buf[8:16]),
262
+ payload: buf,
263
+ }, nil
264
+}
265
+
266
+func (v *AppsLookupResponseView) Item(index uint32) (*AppsLookupItemView, error) {
267
+ if index >= v.ItemCount {
268
+ return nil, ErrOutOfBounds
269
+ }
270
+ dirEnd, ok := lookupBuilderDataOffset(AppsLookupRespHdr, v.ItemCount)
271
+ if !ok {
272
+ return nil, ErrOverflow
273
+ }
274
+ base, ok := lookupDirOffset(AppsLookupRespHdr, index)
275
+ if !ok {
276
+ return nil, ErrOverflow
277
+ }
278
+ off, length, err := lookupDirEntry(v.payload, base)
279
+ if err != nil {
280
+ return nil, err
281
+ }
282
+ item, err := lookupPayloadSlice(v.payload, dirEnd, off, length)
283
+ if err != nil {
284
+ return nil, err
285
+ }
286
+ return decodeAppsLookupItem(item)
287
+}
288
+
289
+func decodeAppsLookupItem(item []byte) (*AppsLookupItemView, error) {
290
+ if len(item) < AppsLookupItemHdr {
291
+ return nil, ErrTruncated
292
+ }
293
+ status := ne.Uint16(item[2:4])
294
+ orchestrator := ne.Uint16(item[4:6])
295
+ cgroupStatus := ne.Uint16(item[6:8])
296
+ pid := ne.Uint32(item[8:12])
297
+ ppid := ne.Uint32(item[12:16])
298
+ uid := ne.Uint32(item[16:20])
299
+ starttime := ne.Uint64(item[24:32])
300
+ commOff, err := checkedWireU32Int(item, 32)
301
+ if err != nil {
302
+ return nil, err
303
+ }
304
+ commLen, err := checkedWireU32Int(item, 36)
305
+ if err != nil {
306
+ return nil, err
307
+ }
308
+ pathOff, err := checkedWireU32Int(item, 40)
309
+ if err != nil {
310
+ return nil, err
311
+ }
312
+ pathLen, err := checkedWireU32Int(item, 44)
313
+ if err != nil {
314
+ return nil, err
315
+ }
316
+ nameOff, err := checkedWireU32Int(item, 48)
317
+ if err != nil {
318
+ return nil, err
319
+ }
320
+ nameLen, err := checkedWireU32Int(item, 52)
321
+ if err != nil {
322
+ return nil, err
323
+ }
324
+ labelCount := ne.Uint16(item[56:58])
325
+ if ne.Uint16(item[0:2]) != 1 || ne.Uint32(item[20:24]) != 0 || ne.Uint16(item[58:60]) != 0 {
326
+ return nil, ErrBadLayout
327
+ }
328
+ if err := validateAppsLookupSemantics(appsLookupSemantics{
329
+ status: status,
330
+ cgroupStatus: cgroupStatus,
331
+ orchestrator: orchestrator,
332
+ ppid: ppid,
333
+ uid: uid,
334
+ starttime: starttime,
335
+ commLen: commLen,
336
+ pathLen: pathLen,
337
+ nameLen: nameLen,
338
+ labelCount: int(labelCount),
339
+ }); err != nil {
340
+ return nil, err
341
+ }
342
+ if status == PidLookupUnknown {
343
+ return decodeAppsLookupUnknownItem(item, pid, commOff, pathOff, nameOff)
344
+ }
345
+ comm, commEnd, err := lookupString(item, AppsLookupItemHdr, commOff, commLen)
346
+ if err != nil {
347
+ return nil, err
348
+ }
349
+ path, pathEnd, err := lookupString(item, AppsLookupItemHdr, pathOff, pathLen)
350
+ if err != nil {
351
+ return nil, err
352
+ }
353
+ name, nameEnd, err := lookupString(item, AppsLookupItemHdr, nameOff, nameLen)
354
+ if err != nil {
355
+ return nil, err
356
+ }
357
+ if overlap(commOff, commEnd, pathOff, pathEnd) || overlap(commOff, commEnd, nameOff, nameEnd) ||
358
+ overlap(pathOff, pathEnd, nameOff, nameEnd) {
359
+ return nil, ErrBadLayout
360
+ }
361
+ table, err := validateLabels(item, AppsLookupItemHdr, labelCount, max(commEnd, max(pathEnd, nameEnd)))
362
+ if err != nil {
363
+ return nil, err
364
+ }
365
+ return &AppsLookupItemView{
366
+ Status: status,
367
+ Orchestrator: orchestrator,
368
+ CgroupStatus: cgroupStatus,
369
+ Pid: pid,
370
+ Ppid: ppid,
371
+ Uid: uid,
372
+ Starttime: starttime,
373
+ Comm: comm,
374
+ CgroupPath: path,
375
+ CgroupName: name,
376
+ LabelCount: labelCount,
377
+ item: item,
378
+ labelTableOffset: table,
379
+ }, nil
380
+}
381
+
382
+func decodeAppsLookupUnknownItem(item []byte, pid uint32, commOff, pathOff, nameOff int) (*AppsLookupItemView, error) {
383
+ comm, commEnd, err := lookupEmptyString(item, AppsLookupItemHdr, commOff)
384
+ if err != nil {
385
+ return nil, err
386
+ }
387
+ path, pathEnd, err := lookupEmptyString(item, AppsLookupItemHdr, pathOff)
388
+ if err != nil {
389
+ return nil, err
390
+ }
391
+ name, nameEnd, err := lookupEmptyString(item, AppsLookupItemHdr, nameOff)
392
+ if err != nil {
393
+ return nil, err
394
+ }
395
+ if overlap(commOff, commEnd, pathOff, pathEnd) || overlap(commOff, commEnd, nameOff, nameEnd) ||
396
+ overlap(pathOff, pathEnd, nameOff, nameEnd) {
397
+ return nil, ErrBadLayout
398
+ }
399
+ labelTableOffset := max(commEnd, max(pathEnd, nameEnd))
400
+ if labelTableOffset != len(item) {
401
+ return nil, ErrBadLayout
402
+ }
403
+ return &AppsLookupItemView{
404
+ Status: PidLookupUnknown,
405
+ CgroupStatus: AppsCgroupKnown,
406
+ Pid: pid,
407
+ Uid: NipcUIDUnset,
408
+ Comm: comm,
409
+ CgroupPath: path,
410
+ CgroupName: name,
411
+ item: item,
412
+ labelTableOffset: labelTableOffset,
413
+ }, nil
414
+}
415
+
416
+func (v *AppsLookupItemView) Label(index uint32) (LookupLabelView, error) {
417
+ return lookupLabelAt(v.item, AppsLookupItemHdr, v.LabelCount, v.labelTableOffset, index)
418
+}
419
+
420
+type AppsLookupBuilder struct {
421
+ buf []byte
422
+ generation uint64
423
+ itemCount uint32
424
+ maxItems uint32
425
+ dataOffset int
426
+ err error
427
+}
428
+
429
+func NewAppsLookupBuilder(buf []byte, maxItems uint32, generation uint64) *AppsLookupBuilder {
430
+ minRequired, ok := lookupBuilderDataOffset(AppsLookupRespHdr, maxItems)
431
+ if !ok {
432
+ panic("AppsLookupBuilder buffer too small")
433
+ }
434
+ if len(buf) < minRequired {
435
+ panic("AppsLookupBuilder buffer too small")
436
+ }
437
+ return &AppsLookupBuilder{buf: buf, generation: generation, maxItems: maxItems, dataOffset: minRequired}
438
+}
439
+
440
+func (b *AppsLookupBuilder) SetGeneration(generation uint64) {
441
+ b.generation = generation
442
+}
443
+
444
+// Add appends one APPS_LOOKUP wire item; parameters mirror the fixed protocol fields.
445
+func (b *AppsLookupBuilder) Add(status, cgroupStatus, orchestrator uint16, pid, ppid, uid uint32, starttime uint64, comm, cgroupPath, cgroupName []byte, labels []struct{ Key, Value []byte }) error { //NOSONAR
446
+ if b.itemCount >= b.maxItems {
447
+ b.err = ErrOverflow
448
+ return ErrOverflow
449
+ }
450
+ if err := validateAppsLookupSemantics(appsLookupSemantics{
451
+ status: status,
452
+ cgroupStatus: cgroupStatus,
453
+ orchestrator: orchestrator,
454
+ ppid: ppid,
455
+ uid: uid,
456
+ starttime: starttime,
457
+ commLen: len(comm),
458
+ pathLen: len(cgroupPath),
459
+ nameLen: len(cgroupName),
460
+ labelCount: len(labels),
461
+ }); err != nil {
462
+ b.err = err
463
+ return err
464
+ }
465
+ if status == PidLookupUnknown {
466
+ return b.addUnknown(pid)
467
+ }
468
+ if invalidSourceString(comm, status == PidLookupKnown) ||
469
+ invalidSourceString(cgroupPath, false) || invalidSourceString(cgroupName, false) {
470
+ b.err = ErrBadLayout
471
+ return ErrBadLayout
472
+ }
473
+ labelCount, ok := checkedU16Int(len(labels))
474
+ if !ok {
475
+ b.err = ErrOverflow
476
+ return ErrOverflow
477
+ }
478
+ itemStart, ok := checkedAlign8(b.dataOffset)
479
+ if !ok {
480
+ b.err = ErrOverflow
481
+ return ErrOverflow
482
+ }
483
+ commOff := AppsLookupItemHdr
484
+ pathOff, ok := checkedAddInt(commOff, len(comm))
485
+ if ok {
486
+ pathOff, ok = checkedAddInt(pathOff, 1)
487
+ }
488
+ if !ok {
489
+ b.err = ErrOverflow
490
+ return ErrOverflow
491
+ }
492
+ nameOff, ok := checkedAddInt(pathOff, len(cgroupPath))
493
+ if ok {
494
+ nameOff, ok = checkedAddInt(nameOff, 1)
495
+ }
496
+ if !ok {
497
+ b.err = ErrOverflow
498
+ return ErrOverflow
499
+ }
500
+ fixedEnd, ok := checkedAddInt(nameOff, len(cgroupName))
501
+ if ok {
502
+ fixedEnd, ok = checkedAddInt(fixedEnd, 1)
503
+ }
504
+ if !ok {
505
+ b.err = ErrOverflow
506
+ return ErrOverflow
507
+ }
508
+ tableStart, tableBytes, itemSize, err := labelLayoutGo(fixedEnd, labels)
509
+ if err != nil {
510
+ b.err = err
511
+ return err
512
+ }
513
+ itemEnd, ok := checkedAddInt(itemStart, itemSize)
514
+ if !ok {
515
+ b.err = ErrOverflow
516
+ return ErrOverflow
517
+ }
518
+ if itemEnd > len(b.buf) {
519
+ b.err = ErrOverflow
520
+ return ErrOverflow
521
+ }
522
+ commOff32, ok := checkedU32Int(commOff)
523
+ if !ok {
524
+ b.err = ErrOverflow
525
+ return ErrOverflow
526
+ }
527
+ commLen32, ok := checkedU32Int(len(comm))
528
+ if !ok {
529
+ b.err = ErrOverflow
530
+ return ErrOverflow
531
+ }
532
+ pathOff32, ok := checkedU32Int(pathOff)
533
+ if !ok {
534
+ b.err = ErrOverflow
535
+ return ErrOverflow
536
+ }
537
+ pathLen32, ok := checkedU32Int(len(cgroupPath))
538
+ if !ok {
539
+ b.err = ErrOverflow
540
+ return ErrOverflow
541
+ }
542
+ nameOff32, ok := checkedU32Int(nameOff)
543
+ if !ok {
544
+ b.err = ErrOverflow
545
+ return ErrOverflow
546
+ }
547
+ nameLen32, ok := checkedU32Int(len(cgroupName))
548
+ if !ok {
549
+ b.err = ErrOverflow
550
+ return ErrOverflow
551
+ }
552
+ itemStart32, ok := checkedU32Int(itemStart)
553
+ if !ok {
554
+ b.err = ErrOverflow
555
+ return ErrOverflow
556
+ }
557
+ itemSize32, ok := checkedU32Int(itemSize)
558
+ if !ok {
559
+ b.err = ErrOverflow
560
+ return ErrOverflow
561
+ }
562
+ clear(b.buf[b.dataOffset:itemStart])
563
+ item := b.buf[itemStart:itemEnd]
564
+ ne.PutUint16(item[0:2], 1)
565
+ ne.PutUint16(item[2:4], status)
566
+ ne.PutUint16(item[4:6], orchestrator)
567
+ ne.PutUint16(item[6:8], cgroupStatus)
568
+ ne.PutUint32(item[8:12], pid)
569
+ ne.PutUint32(item[12:16], ppid)
570
+ ne.PutUint32(item[16:20], uid)
571
+ ne.PutUint32(item[20:24], 0)
572
+ ne.PutUint64(item[24:32], starttime)
573
+ ne.PutUint32(item[32:36], commOff32)
574
+ ne.PutUint32(item[36:40], commLen32)
575
+ ne.PutUint32(item[40:44], pathOff32)
576
+ ne.PutUint32(item[44:48], pathLen32)
577
+ ne.PutUint32(item[48:52], nameOff32)
578
+ ne.PutUint32(item[52:56], nameLen32)
579
+ ne.PutUint16(item[56:58], labelCount)
580
+ ne.PutUint16(item[58:60], 0)
581
+ copy(item[commOff:], comm)
582
+ item[commOff+len(comm)] = 0
583
+ copy(item[pathOff:], cgroupPath)
584
+ item[pathOff+len(cgroupPath)] = 0
585
+ copy(item[nameOff:], cgroupName)
586
+ item[nameOff+len(cgroupName)] = 0
587
+ if len(labels) > 0 {
588
+ clear(item[fixedEnd:tableStart])
589
+ next, err := writeLookupLabels(item, tableStart, tableBytes, labels)
590
+ if err != nil {
591
+ b.err = err
592
+ return err
593
+ }
594
+ itemSize = next
595
+ }
596
+ dir, ok := lookupDirOffset(AppsLookupRespHdr, b.itemCount)
597
+ if !ok {
598
+ b.err = ErrOverflow
599
+ return ErrOverflow
600
+ }
601
+ ne.PutUint32(b.buf[dir:dir+4], itemStart32)
602
+ ne.PutUint32(b.buf[dir+4:dir+8], itemSize32)
603
+ b.dataOffset = itemStart + itemSize
604
+ b.itemCount++
605
+ return nil
606
+}
607
+
608
+func (b *AppsLookupBuilder) addUnknown(pid uint32) error {
609
+ itemStart, ok := checkedAlign8(b.dataOffset)
610
+ if !ok {
611
+ b.err = ErrOverflow
612
+ return ErrOverflow
613
+ }
614
+ itemEnd, ok := checkedAddInt(itemStart, appsLookupUnknownItemSize)
615
+ if !ok || itemEnd > len(b.buf) {
616
+ b.err = ErrOverflow
617
+ return ErrOverflow
618
+ }
619
+ itemStart32, ok := checkedU32Int(itemStart)
620
+ if !ok {
621
+ b.err = ErrOverflow
622
+ return ErrOverflow
623
+ }
624
+ itemSize32, ok := checkedU32Int(appsLookupUnknownItemSize)
625
+ if !ok {
626
+ b.err = ErrOverflow
627
+ return ErrOverflow
628
+ }
629
+ clear(b.buf[b.dataOffset:itemStart])
630
+ item := b.buf[itemStart:itemEnd]
631
+ clear(item)
632
+ ne.PutUint16(item[0:2], 1)
633
+ ne.PutUint16(item[2:4], PidLookupUnknown)
634
+ ne.PutUint32(item[8:12], pid)
635
+ ne.PutUint32(item[16:20], NipcUIDUnset)
636
+ ne.PutUint32(item[32:36], AppsLookupItemHdr)
637
+ ne.PutUint32(item[40:44], AppsLookupItemHdr+1)
638
+ ne.PutUint32(item[48:52], AppsLookupItemHdr+2)
639
+
640
+ dir, ok := lookupDirOffset(AppsLookupRespHdr, b.itemCount)
641
+ if !ok {
642
+ b.err = ErrOverflow
643
+ return ErrOverflow
644
+ }
645
+ ne.PutUint32(b.buf[dir:dir+4], itemStart32)
646
+ ne.PutUint32(b.buf[dir+4:dir+8], itemSize32)
647
+ b.dataOffset = itemEnd
648
+ b.itemCount++
649
+ return nil
650
+}
651
+
652
+func (b *AppsLookupBuilder) Finish() int {
653
+ return finishLookupResponse(b.buf, AppsLookupRespHdr, b.itemCount, b.dataOffset, b.generation)
654
+}
655
+
656
+func (b *AppsLookupBuilder) Error() error {
657
+ return b.err
658
+}
659
+
660
+func (b *AppsLookupBuilder) ItemCount() uint32 {
661
+ return b.itemCount
662
+}
663
+
664
+func DispatchAppsLookup(req []byte, resp []byte, handler func(*AppsLookupRequestView, *AppsLookupBuilder) bool) (int, error) {
665
+ request, err := DecodeAppsLookupRequest(req)
666
+ if err != nil {
667
+ return 0, err
668
+ }
669
+ minRequired, ok := lookupBuilderDataOffset(AppsLookupRespHdr, request.ItemCount)
670
+ if !ok || len(resp) < minRequired {
671
+ return 0, ErrOverflow
672
+ }
673
+ builder := NewAppsLookupBuilder(resp, request.ItemCount, 0)
674
+ if !handler(request, builder) {
675
+ if builder.Error() != nil {
676
+ return 0, builder.Error()
677
+ }
678
+ return 0, ErrBadLayout
679
+ }
680
+ if builder.Error() != nil {
681
+ return 0, builder.Error()
682
+ }
683
+ if builder.itemCount != request.ItemCount {
684
+ return 0, ErrBadItemCount
685
+ }
686
+ n := builder.Finish()
687
+ if n == 0 {
688
+ return 0, ErrOverflow
689
+ }
690
+ return n, nil
691
+}
src/go/pkg/netipc/protocol/cgroups_lookup.go
new
+593
@@ -0,0 +1,593 @@
1
+package protocol
2
+
3
+import "bytes"
4
+
5
+const (
6
+ CgroupLookupKnown uint16 = 0
7
+ CgroupLookupUnknownRetryLater uint16 = 1
8
+ CgroupLookupUnknownPermanent uint16 = 2
9
+
10
+ CgroupsLookupReqHdr = 16
11
+ CgroupsLookupRespHdr = 16
12
+ CgroupsLookupItemHdr = 28
13
+
14
+ cgroupsLookupUnknownFixedBytes = CgroupsLookupItemHdr + 1
15
+)
16
+
17
+type CgroupsLookupRequestView struct {
18
+ ItemCount uint32
19
+ payload []byte
20
+}
21
+
22
+type CgroupsLookupResponseView struct {
23
+ LayoutVersion uint16
24
+ Flags uint16
25
+ ItemCount uint32
26
+ Generation uint64
27
+ payload []byte
28
+}
29
+
30
+type CgroupsLookupItemView struct {
31
+ Status uint16
32
+ Orchestrator uint16
33
+ Path CStringView
34
+ Name CStringView
35
+ LabelCount uint16
36
+ item []byte
37
+ labelTableOffset int
38
+}
39
+
40
+func validateCgroupsLookupSemantics(status, orchestrator uint16, pathLen, nameLen, labelCount int) error {
41
+ if status != CgroupLookupKnown && status != CgroupLookupUnknownRetryLater && status != CgroupLookupUnknownPermanent {
42
+ return ErrBadLayout
43
+ }
44
+ if pathLen == 0 {
45
+ return ErrBadLayout
46
+ }
47
+ if status != CgroupLookupKnown && (orchestrator != 0 || nameLen != 0 || labelCount != 0) {
48
+ return ErrBadLayout
49
+ }
50
+ return nil
51
+}
52
+
53
+func EncodeCgroupsLookupRequest(paths [][]byte, buf []byte) (int, error) {
54
+ count := len(paths)
55
+ if uint64(count) > uint64(^uint32(0)) {
56
+ return 0, ErrOverflow
57
+ }
58
+ dirSize, ok := checkedMulInt(count, LookupDirEntrySize)
59
+ if !ok {
60
+ return 0, ErrOverflow
61
+ }
62
+ packedStart, ok := checkedAddInt(CgroupsLookupReqHdr, dirSize)
63
+ if !ok {
64
+ return 0, ErrOverflow
65
+ }
66
+ if len(buf) < packedStart {
67
+ return 0, ErrOverflow
68
+ }
69
+ data := packedStart
70
+ for i, path := range paths {
71
+ if invalidSourceString(path, true) {
72
+ return 0, ErrBadLayout
73
+ }
74
+ aligned, ok := checkedAlign8(data)
75
+ if !ok {
76
+ return 0, ErrOverflow
77
+ }
78
+ keyLen, ok := checkedAddInt(len(path), 1)
79
+ if !ok {
80
+ return 0, ErrOverflow
81
+ }
82
+ end, ok := checkedAddInt(aligned, keyLen)
83
+ if !ok {
84
+ return 0, ErrOverflow
85
+ }
86
+ if end > len(buf) {
87
+ return 0, ErrOverflow
88
+ }
89
+ clear(buf[data:aligned])
90
+ offset32, ok := checkedU32Int(aligned - packedStart)
91
+ if !ok {
92
+ return 0, ErrOverflow
93
+ }
94
+ keyLen32, ok := checkedU32Int(keyLen)
95
+ if !ok {
96
+ return 0, ErrOverflow
97
+ }
98
+ base := CgroupsLookupReqHdr + i*LookupDirEntrySize
99
+ ne.PutUint32(buf[base:base+4], offset32)
100
+ ne.PutUint32(buf[base+4:base+8], keyLen32)
101
+ copy(buf[aligned:], path)
102
+ buf[aligned+len(path)] = 0
103
+ data = end
104
+ }
105
+ ne.PutUint16(buf[0:2], 1)
106
+ ne.PutUint16(buf[2:4], 0)
107
+ ne.PutUint32(buf[4:8], uint32(count))
108
+ ne.PutUint32(buf[8:12], 0)
109
+ ne.PutUint32(buf[12:16], 0)
110
+ return data, nil
111
+}
112
+
113
+func DecodeCgroupsLookupRequest(buf []byte) (*CgroupsLookupRequestView, error) {
114
+ if len(buf) < CgroupsLookupReqHdr {
115
+ return nil, ErrTruncated
116
+ }
117
+ if ne.Uint16(buf[0:2]) != 1 || ne.Uint16(buf[2:4]) != 0 ||
118
+ ne.Uint32(buf[8:12]) != 0 || ne.Uint32(buf[12:16]) != 0 {
119
+ return nil, ErrBadLayout
120
+ }
121
+ itemCount := ne.Uint32(buf[4:8])
122
+ dirSize64 := uint64(itemCount) * uint64(LookupDirEntrySize)
123
+ dirEnd, ok := checkedInt(uint64(CgroupsLookupReqHdr) + dirSize64)
124
+ if !ok {
125
+ return nil, ErrBadItemCount
126
+ }
127
+ if dirEnd > len(buf) {
128
+ return nil, ErrTruncated
129
+ }
130
+ if err := validateLookupDir(buf, CgroupsLookupReqHdr, itemCount, len(buf)-dirEnd, 2, -1); err != nil {
131
+ return nil, err
132
+ }
133
+ for i := range itemCount {
134
+ base := CgroupsLookupReqHdr + int(i)*LookupDirEntrySize
135
+ off, length, err := lookupDirEntry(buf, base)
136
+ if err != nil {
137
+ return nil, err
138
+ }
139
+ key, err := lookupPayloadSlice(buf, dirEnd, off, length)
140
+ if err != nil {
141
+ return nil, err
142
+ }
143
+ if key[length-1] != 0 {
144
+ return nil, ErrMissingNul
145
+ }
146
+ if bytes.Contains(key[:length-1], []byte{0}) {
147
+ return nil, ErrBadLayout
148
+ }
149
+ }
150
+ return &CgroupsLookupRequestView{ItemCount: itemCount, payload: buf}, nil
151
+}
152
+
153
+func (v *CgroupsLookupRequestView) Item(index uint32) (CStringView, error) {
154
+ if index >= v.ItemCount {
155
+ return CStringView{}, ErrOutOfBounds
156
+ }
157
+ dirEnd, ok := lookupBuilderDataOffset(CgroupsLookupReqHdr, v.ItemCount)
158
+ if !ok {
159
+ return CStringView{}, ErrOverflow
160
+ }
161
+ base, ok := lookupDirOffset(CgroupsLookupReqHdr, index)
162
+ if !ok {
163
+ return CStringView{}, ErrOverflow
164
+ }
165
+ off, length, err := lookupDirEntry(v.payload, base)
166
+ if err != nil {
167
+ return CStringView{}, err
168
+ }
169
+ item, err := lookupPayloadSlice(v.payload, dirEnd, off, length)
170
+ if err != nil {
171
+ return CStringView{}, err
172
+ }
173
+ stringLen, ok := checkedU32Int(length - 1)
174
+ if !ok {
175
+ return CStringView{}, ErrOutOfBounds
176
+ }
177
+ return NewCStringView(item, stringLen), nil
178
+}
179
+
180
+func DecodeCgroupsLookupResponse(buf []byte) (*CgroupsLookupResponseView, error) {
181
+ if len(buf) < CgroupsLookupRespHdr {
182
+ return nil, ErrTruncated
183
+ }
184
+ if ne.Uint16(buf[0:2]) != 1 || ne.Uint16(buf[2:4]) != 0 {
185
+ return nil, ErrBadLayout
186
+ }
187
+ itemCount := ne.Uint32(buf[4:8])
188
+ dirEnd, ok := checkedInt(uint64(CgroupsLookupRespHdr) + uint64(itemCount)*uint64(LookupDirEntrySize))
189
+ if !ok {
190
+ return nil, ErrBadItemCount
191
+ }
192
+ if dirEnd > len(buf) {
193
+ return nil, ErrTruncated
194
+ }
195
+ if err := validateLookupDir(buf, CgroupsLookupRespHdr, itemCount, len(buf)-dirEnd, CgroupsLookupItemHdr, -1); err != nil {
196
+ return nil, err
197
+ }
198
+ for i := range itemCount {
199
+ base := CgroupsLookupRespHdr + int(i)*LookupDirEntrySize
200
+ off, length, err := lookupDirEntry(buf, base)
201
+ if err != nil {
202
+ return nil, err
203
+ }
204
+ item, err := lookupPayloadSlice(buf, dirEnd, off, length)
205
+ if err != nil {
206
+ return nil, err
207
+ }
208
+ if _, err := decodeCgroupsLookupItem(item); err != nil {
209
+ return nil, err
210
+ }
211
+ }
212
+ return &CgroupsLookupResponseView{
213
+ LayoutVersion: 1,
214
+ Flags: 0,
215
+ ItemCount: itemCount,
216
+ Generation: ne.Uint64(buf[8:16]),
217
+ payload: buf,
218
+ }, nil
219
+}
220
+
221
+func (v *CgroupsLookupResponseView) Item(index uint32) (*CgroupsLookupItemView, error) {
222
+ if index >= v.ItemCount {
223
+ return nil, ErrOutOfBounds
224
+ }
225
+ dirEnd, ok := lookupBuilderDataOffset(CgroupsLookupRespHdr, v.ItemCount)
226
+ if !ok {
227
+ return nil, ErrOverflow
228
+ }
229
+ base, ok := lookupDirOffset(CgroupsLookupRespHdr, index)
230
+ if !ok {
231
+ return nil, ErrOverflow
232
+ }
233
+ off, length, err := lookupDirEntry(v.payload, base)
234
+ if err != nil {
235
+ return nil, err
236
+ }
237
+ item, err := lookupPayloadSlice(v.payload, dirEnd, off, length)
238
+ if err != nil {
239
+ return nil, err
240
+ }
241
+ return decodeCgroupsLookupItem(item)
242
+}
243
+
244
+func decodeCgroupsLookupItem(item []byte) (*CgroupsLookupItemView, error) {
245
+ if len(item) < CgroupsLookupItemHdr {
246
+ return nil, ErrTruncated
247
+ }
248
+ status := ne.Uint16(item[2:4])
249
+ orchestrator := ne.Uint16(item[4:6])
250
+ pathOff, err := checkedWireU32Int(item, 8)
251
+ if err != nil {
252
+ return nil, err
253
+ }
254
+ pathLen, err := checkedWireU32Int(item, 12)
255
+ if err != nil {
256
+ return nil, err
257
+ }
258
+ nameOff, err := checkedWireU32Int(item, 16)
259
+ if err != nil {
260
+ return nil, err
261
+ }
262
+ nameLen, err := checkedWireU32Int(item, 20)
263
+ if err != nil {
264
+ return nil, err
265
+ }
266
+ labelCount := ne.Uint16(item[24:26])
267
+ if ne.Uint16(item[0:2]) != 1 || ne.Uint16(item[6:8]) != 0 || ne.Uint16(item[26:28]) != 0 {
268
+ return nil, ErrBadLayout
269
+ }
270
+ if err := validateCgroupsLookupSemantics(status, orchestrator, pathLen, nameLen, int(labelCount)); err != nil {
271
+ return nil, err
272
+ }
273
+ if status != CgroupLookupKnown {
274
+ return decodeCgroupsLookupUnknownItem(item, status, pathOff, pathLen, nameOff)
275
+ }
276
+ path, pathEnd, err := lookupString(item, CgroupsLookupItemHdr, pathOff, pathLen)
277
+ if err != nil {
278
+ return nil, err
279
+ }
280
+ name, nameEnd, err := lookupString(item, CgroupsLookupItemHdr, nameOff, nameLen)
281
+ if err != nil {
282
+ return nil, err
283
+ }
284
+ if overlap(pathOff, pathEnd, nameOff, nameEnd) {
285
+ return nil, ErrBadLayout
286
+ }
287
+ table, err := validateLabels(item, CgroupsLookupItemHdr, labelCount, max(pathEnd, nameEnd))
288
+ if err != nil {
289
+ return nil, err
290
+ }
291
+ return &CgroupsLookupItemView{
292
+ Status: status,
293
+ Orchestrator: orchestrator,
294
+ Path: path,
295
+ Name: name,
296
+ LabelCount: labelCount,
297
+ item: item,
298
+ labelTableOffset: table,
299
+ }, nil
300
+}
301
+
302
+func decodeCgroupsLookupUnknownItem(item []byte, status uint16, pathOff, pathLen, nameOff int) (*CgroupsLookupItemView, error) {
303
+ path, pathEnd, err := lookupString(item, CgroupsLookupItemHdr, pathOff, pathLen)
304
+ if err != nil {
305
+ return nil, err
306
+ }
307
+ name, nameEnd, err := lookupEmptyString(item, CgroupsLookupItemHdr, nameOff)
308
+ if err != nil {
309
+ return nil, err
310
+ }
311
+ if overlap(pathOff, pathEnd, nameOff, nameEnd) {
312
+ return nil, ErrBadLayout
313
+ }
314
+ labelTableOffset := max(pathEnd, nameEnd)
315
+ if labelTableOffset != len(item) {
316
+ return nil, ErrBadLayout
317
+ }
318
+ return &CgroupsLookupItemView{
319
+ Status: status,
320
+ Path: path,
321
+ Name: name,
322
+ item: item,
323
+ labelTableOffset: labelTableOffset,
324
+ }, nil
325
+}
326
+
327
+func (v *CgroupsLookupItemView) Label(index uint32) (LookupLabelView, error) {
328
+ return lookupLabelAt(v.item, CgroupsLookupItemHdr, v.LabelCount, v.labelTableOffset, index)
329
+}
330
+
331
+type CgroupsLookupBuilder struct {
332
+ buf []byte
333
+ generation uint64
334
+ itemCount uint32
335
+ maxItems uint32
336
+ dataOffset int
337
+ err error
338
+}
339
+
340
+func NewCgroupsLookupBuilder(buf []byte, maxItems uint32, generation uint64) *CgroupsLookupBuilder {
341
+ minRequired, ok := lookupBuilderDataOffset(CgroupsLookupRespHdr, maxItems)
342
+ if !ok {
343
+ panic("CgroupsLookupBuilder buffer too small")
344
+ }
345
+ if len(buf) < minRequired {
346
+ panic("CgroupsLookupBuilder buffer too small")
347
+ }
348
+ return &CgroupsLookupBuilder{buf: buf, generation: generation, maxItems: maxItems, dataOffset: minRequired}
349
+}
350
+
351
+func (b *CgroupsLookupBuilder) SetGeneration(generation uint64) {
352
+ b.generation = generation
353
+}
354
+
355
+func (b *CgroupsLookupBuilder) Add(status, orchestrator uint16, path, name []byte, labels []struct{ Key, Value []byte }) error {
356
+ if b.itemCount >= b.maxItems {
357
+ b.err = ErrOverflow
358
+ return ErrOverflow
359
+ }
360
+ if err := validateCgroupsLookupSemantics(status, orchestrator, len(path), len(name), len(labels)); err != nil {
361
+ b.err = err
362
+ return err
363
+ }
364
+ if invalidSourceString(path, true) || invalidSourceString(name, false) {
365
+ b.err = ErrBadLayout
366
+ return ErrBadLayout
367
+ }
368
+ if status != CgroupLookupKnown {
369
+ return b.addUnknown(status, path)
370
+ }
371
+ labelCount, ok := checkedU16Int(len(labels))
372
+ if !ok {
373
+ b.err = ErrOverflow
374
+ return ErrOverflow
375
+ }
376
+ itemStart, ok := checkedAlign8(b.dataOffset)
377
+ if !ok {
378
+ b.err = ErrOverflow
379
+ return ErrOverflow
380
+ }
381
+ pathOff := CgroupsLookupItemHdr
382
+ nameOff, ok := checkedAddInt(pathOff, len(path))
383
+ if ok {
384
+ nameOff, ok = checkedAddInt(nameOff, 1)
385
+ }
386
+ if !ok {
387
+ b.err = ErrOverflow
388
+ return ErrOverflow
389
+ }
390
+ fixedEnd, ok := checkedAddInt(nameOff, len(name))
391
+ if ok {
392
+ fixedEnd, ok = checkedAddInt(fixedEnd, 1)
393
+ }
394
+ if !ok {
395
+ b.err = ErrOverflow
396
+ return ErrOverflow
397
+ }
398
+ tableStart, tableBytes, itemSize, err := labelLayoutGo(fixedEnd, labels)
399
+ if err != nil {
400
+ b.err = err
401
+ return err
402
+ }
403
+ itemEnd, ok := checkedAddInt(itemStart, itemSize)
404
+ if !ok {
405
+ b.err = ErrOverflow
406
+ return ErrOverflow
407
+ }
408
+ if itemEnd > len(b.buf) {
409
+ b.err = ErrOverflow
410
+ return ErrOverflow
411
+ }
412
+ pathOff32, ok := checkedU32Int(pathOff)
413
+ if !ok {
414
+ b.err = ErrOverflow
415
+ return ErrOverflow
416
+ }
417
+ pathLen32, ok := checkedU32Int(len(path))
418
+ if !ok {
419
+ b.err = ErrOverflow
420
+ return ErrOverflow
421
+ }
422
+ nameOff32, ok := checkedU32Int(nameOff)
423
+ if !ok {
424
+ b.err = ErrOverflow
425
+ return ErrOverflow
426
+ }
427
+ nameLen32, ok := checkedU32Int(len(name))
428
+ if !ok {
429
+ b.err = ErrOverflow
430
+ return ErrOverflow
431
+ }
432
+ itemStart32, ok := checkedU32Int(itemStart)
433
+ if !ok {
434
+ b.err = ErrOverflow
435
+ return ErrOverflow
436
+ }
437
+ itemSize32, ok := checkedU32Int(itemSize)
438
+ if !ok {
439
+ b.err = ErrOverflow
440
+ return ErrOverflow
441
+ }
442
+ clear(b.buf[b.dataOffset:itemStart])
443
+ item := b.buf[itemStart:itemEnd]
444
+ ne.PutUint16(item[0:2], 1)
445
+ ne.PutUint16(item[2:4], status)
446
+ ne.PutUint16(item[4:6], orchestrator)
447
+ ne.PutUint16(item[6:8], 0)
448
+ ne.PutUint32(item[8:12], pathOff32)
449
+ ne.PutUint32(item[12:16], pathLen32)
450
+ ne.PutUint32(item[16:20], nameOff32)
451
+ ne.PutUint32(item[20:24], nameLen32)
452
+ ne.PutUint16(item[24:26], labelCount)
453
+ ne.PutUint16(item[26:28], 0)
454
+ copy(item[pathOff:], path)
455
+ item[pathOff+len(path)] = 0
456
+ copy(item[nameOff:], name)
457
+ item[nameOff+len(name)] = 0
458
+ if len(labels) > 0 {
459
+ clear(item[fixedEnd:tableStart])
460
+ next, err := writeLookupLabels(item, tableStart, tableBytes, labels)
461
+ if err != nil {
462
+ b.err = err
463
+ return err
464
+ }
465
+ itemSize = next
466
+ }
467
+ dir, ok := lookupDirOffset(CgroupsLookupRespHdr, b.itemCount)
468
+ if !ok {
469
+ b.err = ErrOverflow
470
+ return ErrOverflow
471
+ }
472
+ ne.PutUint32(b.buf[dir:dir+4], itemStart32)
473
+ ne.PutUint32(b.buf[dir+4:dir+8], itemSize32)
474
+ b.dataOffset = itemStart + itemSize
475
+ b.itemCount++
476
+ return nil
477
+}
478
+
479
+func (b *CgroupsLookupBuilder) addUnknown(status uint16, path []byte) error {
480
+ itemStart, ok := checkedAlign8(b.dataOffset)
481
+ if !ok {
482
+ b.err = ErrOverflow
483
+ return ErrOverflow
484
+ }
485
+ nameOff, ok := checkedAddInt(CgroupsLookupItemHdr, len(path))
486
+ if ok {
487
+ nameOff, ok = checkedAddInt(nameOff, 1)
488
+ }
489
+ if !ok {
490
+ b.err = ErrOverflow
491
+ return ErrOverflow
492
+ }
493
+ itemSize, ok := checkedAddInt(nameOff, 1)
494
+ if !ok {
495
+ b.err = ErrOverflow
496
+ return ErrOverflow
497
+ }
498
+ itemEnd, ok := checkedAddInt(itemStart, itemSize)
499
+ if !ok {
500
+ b.err = ErrOverflow
501
+ return ErrOverflow
502
+ }
503
+ if itemEnd > len(b.buf) {
504
+ b.err = ErrOverflow
505
+ return ErrOverflow
506
+ }
507
+ pathLen32, ok := checkedU32Int(len(path))
508
+ if !ok {
509
+ b.err = ErrOverflow
510
+ return ErrOverflow
511
+ }
512
+ nameOff32, ok := checkedU32Int(nameOff)
513
+ if !ok {
514
+ b.err = ErrOverflow
515
+ return ErrOverflow
516
+ }
517
+ itemStart32, ok := checkedU32Int(itemStart)
518
+ if !ok {
519
+ b.err = ErrOverflow
520
+ return ErrOverflow
521
+ }
522
+ itemSize32, ok := checkedU32Int(itemSize)
523
+ if !ok {
524
+ b.err = ErrOverflow
525
+ return ErrOverflow
526
+ }
527
+ clear(b.buf[b.dataOffset:itemStart])
528
+ item := b.buf[itemStart:itemEnd]
529
+ ne.PutUint16(item[0:2], 1)
530
+ ne.PutUint16(item[2:4], status)
531
+ ne.PutUint16(item[4:6], 0)
532
+ ne.PutUint16(item[6:8], 0)
533
+ ne.PutUint32(item[8:12], CgroupsLookupItemHdr)
534
+ ne.PutUint32(item[12:16], pathLen32)
535
+ ne.PutUint32(item[16:20], nameOff32)
536
+ ne.PutUint32(item[20:24], 0)
537
+ ne.PutUint16(item[24:26], 0)
538
+ ne.PutUint16(item[26:28], 0)
539
+ copy(item[CgroupsLookupItemHdr:], path)
540
+ item[CgroupsLookupItemHdr+len(path)] = 0
541
+ item[nameOff] = 0
542
+ dir, ok := lookupDirOffset(CgroupsLookupRespHdr, b.itemCount)
543
+ if !ok {
544
+ b.err = ErrOverflow
545
+ return ErrOverflow
546
+ }
547
+ ne.PutUint32(b.buf[dir:dir+4], itemStart32)
548
+ ne.PutUint32(b.buf[dir+4:dir+8], itemSize32)
549
+ b.dataOffset = itemStart + itemSize
550
+ b.itemCount++
551
+ return nil
552
+}
553
+
554
+func (b *CgroupsLookupBuilder) Finish() int {
555
+ return finishLookupResponse(b.buf, CgroupsLookupRespHdr, b.itemCount, b.dataOffset, b.generation)
556
+}
557
+
558
+func (b *CgroupsLookupBuilder) Error() error {
559
+ return b.err
560
+}
561
+
562
+func (b *CgroupsLookupBuilder) ItemCount() uint32 {
563
+ return b.itemCount
564
+}
565
+
566
+func DispatchCgroupsLookup(req []byte, resp []byte, handler func(*CgroupsLookupRequestView, *CgroupsLookupBuilder) bool) (int, error) {
567
+ request, err := DecodeCgroupsLookupRequest(req)
568
+ if err != nil {
569
+ return 0, err
570
+ }
571
+ minRequired, ok := lookupBuilderDataOffset(CgroupsLookupRespHdr, request.ItemCount)
572
+ if !ok || len(resp) < minRequired {
573
+ return 0, ErrOverflow
574
+ }
575
+ builder := NewCgroupsLookupBuilder(resp, request.ItemCount, 0)
576
+ if !handler(request, builder) {
577
+ if builder.Error() != nil {
578
+ return 0, builder.Error()
579
+ }
580
+ return 0, ErrBadLayout
581
+ }
582
+ if builder.Error() != nil {
583
+ return 0, builder.Error()
584
+ }
585
+ if builder.itemCount != request.ItemCount {
586
+ return 0, ErrBadItemCount
587
+ }
588
+ n := builder.Finish()
589
+ if n == 0 {
590
+ return 0, ErrOverflow
591
+ }
592
+ return n, nil
593
+}
src/go/pkg/netipc/protocol/cgroups_snapshot.go
renamed
+203
-62
@@ -6,6 +6,13 @@ import (
6
"fmt"
7
)
8
9
+const (
10
+ cgroupsReqSize = 4
11
+ cgroupsRespHdr = 24
12
+ cgroupsDirEntry = 8
13
+ cgroupsItemHdr = 32
14
+)
15
+
16
// ---------------------------------------------------------------------------
17
// Cgroups snapshot request (4 bytes)
18
// ---------------------------------------------------------------------------
@@ -142,30 +149,42 @@ func DecodeCgroupsResponse(buf []byte) (CgroupsResponseView, error) {
149
return CgroupsResponseView{}, ErrBadLayout
150
}
151
145
- // Validate directory fits (use uint64 to prevent int overflow on 32-bit).
152
dirSize64 := uint64(itemCount) * uint64(cgroupsDirEntry)
153
dirEnd64 := uint64(cgroupsRespHdr) + dirSize64
148
- if dirEnd64 > uint64(len(buf)) {
154
+ dirEnd, ok := checkedInt(dirEnd64)
155
+ if !ok {
156
+ return CgroupsResponseView{}, ErrBadItemCount
157
+ }
158
+ if dirEnd > len(buf) {
159
return CgroupsResponseView{}, ErrTruncated
160
}
151
- dirEnd := int(dirEnd64)
161
162
packedAreaLen := len(buf) - dirEnd
163
164
// Validate each directory entry.
156
- dirSize := int(dirSize64)
165
+ dirSize, ok := checkedInt(dirSize64)
166
+ if !ok {
167
+ return CgroupsResponseView{}, ErrBadItemCount
168
+ }
169
for i := 0; i < dirSize; i += cgroupsDirEntry {
170
base := cgroupsRespHdr + i
159
- off := ne.Uint32(buf[base : base+4])
160
- length := ne.Uint32(buf[base+4 : base+8])
171
+ off, err := checkedWireU32Int(buf, base)
172
+ if err != nil {
173
+ return CgroupsResponseView{}, err
174
+ }
175
+ length, err := checkedWireU32Int(buf, base+4)
176
+ if err != nil {
177
+ return CgroupsResponseView{}, err
178
+ }
179
162
- if int(off)%Alignment != 0 {
180
+ if off%Alignment != 0 {
181
return CgroupsResponseView{}, ErrBadAlignment
182
}
165
- if uint64(off)+uint64(length) > uint64(packedAreaLen) {
183
+ end, ok := checkedAddInt(off, length)
184
+ if !ok || end > packedAreaLen {
185
return CgroupsResponseView{}, ErrOutOfBounds
186
}
168
- if int(length) < cgroupsItemHdr {
187
+ if length < cgroupsItemHdr {
188
return CgroupsResponseView{}, ErrTruncated
189
}
190
}
@@ -188,15 +207,41 @@ func (v *CgroupsResponseView) Item(index uint32) (CgroupsItemView, error) {
207
}
208
209
dirStart := cgroupsRespHdr
191
- dirSize := int(uint64(v.ItemCount) * uint64(cgroupsDirEntry))
192
- packedAreaStart := dirStart + dirSize
210
+ dirSize, ok := checkedInt(uint64(v.ItemCount) * uint64(cgroupsDirEntry))
211
+ if !ok {
212
+ return CgroupsItemView{}, ErrBadItemCount
213
+ }
214
+ packedAreaStart, ok := checkedAddInt(dirStart, dirSize)
215
+ if !ok {
216
+ return CgroupsItemView{}, ErrOutOfBounds
217
+ }
218
194
- dirBase := dirStart + int(index)*cgroupsDirEntry
195
- itemOff := int(ne.Uint32(v.payload[dirBase : dirBase+4]))
196
- itemLen := int(ne.Uint32(v.payload[dirBase+4 : dirBase+8]))
219
+ dirIndexOff, ok := checkedInt(uint64(index) * uint64(cgroupsDirEntry))
220
+ if !ok {
221
+ return CgroupsItemView{}, ErrOutOfBounds
222
+ }
223
+ dirBase, ok := checkedAddInt(dirStart, dirIndexOff)
224
+ if !ok {
225
+ return CgroupsItemView{}, ErrOutOfBounds
226
+ }
227
+ itemOff, err := checkedWireU32Int(v.payload, dirBase)
228
+ if err != nil {
229
+ return CgroupsItemView{}, err
230
+ }
231
+ itemLen, err := checkedWireU32Int(v.payload, dirBase+4)
232
+ if err != nil {
233
+ return CgroupsItemView{}, err
234
+ }
235
198
- itemStart := packedAreaStart + itemOff
199
- item := v.payload[itemStart : itemStart+itemLen]
236
+ itemStart, ok := checkedAddInt(packedAreaStart, itemOff)
237
+ if !ok {
238
+ return CgroupsItemView{}, ErrOutOfBounds
239
+ }
240
+ itemEnd, ok := checkedAddInt(itemStart, itemLen)
241
+ if !ok || itemEnd > len(v.payload) {
242
+ return CgroupsItemView{}, ErrOutOfBounds
243
+ }
244
+ item := v.payload[itemStart:itemEnd]
245
246
layoutVersion := ne.Uint16(item[0:2])
247
flags := ne.Uint16(item[2:4])
@@ -204,10 +249,24 @@ func (v *CgroupsResponseView) Item(index uint32) (CgroupsItemView, error) {
249
options := ne.Uint32(item[8:12])
250
enabled := ne.Uint32(item[12:16])
251
207
- nameOff := int(ne.Uint32(item[16:20]))
208
- nameLen := ne.Uint32(item[20:24])
209
- pathOff := int(ne.Uint32(item[24:28]))
210
- pathLen := ne.Uint32(item[28:32])
252
+ nameOff, err := checkedWireU32Int(item, 16)
253
+ if err != nil {
254
+ return CgroupsItemView{}, err
255
+ }
256
+ nameLen, err := checkedWireU32Int(item, 20)
257
+ if err != nil {
258
+ return CgroupsItemView{}, err
259
+ }
260
+ nameLen32 := ne.Uint32(item[20:24])
261
+ pathOff, err := checkedWireU32Int(item, 24)
262
+ if err != nil {
263
+ return CgroupsItemView{}, err
264
+ }
265
+ pathLen, err := checkedWireU32Int(item, 28)
266
+ if err != nil {
267
+ return CgroupsItemView{}, err
268
+ }
269
+ pathLen32 := ne.Uint32(item[28:32])
270
271
if layoutVersion != 1 {
272
return CgroupsItemView{}, ErrBadLayout
@@ -222,10 +281,15 @@ func (v *CgroupsResponseView) Item(index uint32) (CgroupsItemView, error) {
281
if nameOff < cgroupsItemHdr {
282
return CgroupsItemView{}, ErrOutOfBounds
283
}
225
- if uint64(nameOff)+uint64(nameLen)+1 > uint64(itemLen) {
284
+ nameEnd, ok := checkedAddInt(nameOff, nameLen)
285
+ if !ok {
286
+ return CgroupsItemView{}, ErrOutOfBounds
287
+ }
288
+ nameNulEnd, ok := checkedAddInt(nameEnd, 1)
289
+ if !ok || nameNulEnd > itemLen {
290
return CgroupsItemView{}, ErrOutOfBounds
291
}
228
- if item[nameOff+int(nameLen)] != 0 {
292
+ if item[nameEnd] != 0 {
293
return CgroupsItemView{}, ErrMissingNul
294
}
295
@@ -233,26 +297,27 @@ func (v *CgroupsResponseView) Item(index uint32) (CgroupsItemView, error) {
297
if pathOff < cgroupsItemHdr {
298
return CgroupsItemView{}, ErrOutOfBounds
299
}
236
- if uint64(pathOff)+uint64(pathLen)+1 > uint64(itemLen) {
300
+ pathEnd, ok := checkedAddInt(pathOff, pathLen)
301
+ if !ok {
302
+ return CgroupsItemView{}, ErrOutOfBounds
303
+ }
304
+ pathNulEnd, ok := checkedAddInt(pathEnd, 1)
305
+ if !ok || pathNulEnd > itemLen {
306
return CgroupsItemView{}, ErrOutOfBounds
307
}
239
- if item[pathOff+int(pathLen)] != 0 {
308
+ if item[pathEnd] != 0 {
309
return CgroupsItemView{}, ErrMissingNul
310
}
311
312
// Reject overlapping name and path regions (including NUL)
313
{
245
- nameStart := uint64(nameOff)
246
- nameEnd := nameStart + uint64(nameLen) + 1
247
- pathStart := uint64(pathOff)
248
- pathEnd := pathStart + uint64(pathLen) + 1
249
- if nameStart < pathEnd && pathStart < nameEnd {
314
+ if overlap(nameOff, nameNulEnd, pathOff, pathNulEnd) {
315
return CgroupsItemView{}, ErrBadLayout
316
}
317
}
318
254
- name := NewCStringView(item[nameOff:nameOff+int(nameLen)+1], nameLen)
255
- path := NewCStringView(item[pathOff:pathOff+int(pathLen)+1], pathLen)
319
+ name := NewCStringView(item[nameOff:nameNulEnd], nameLen32)
320
+ path := NewCStringView(item[pathOff:pathNulEnd], pathLen32)
321
322
return CgroupsItemView{
323
LayoutVersion: layoutVersion,
@@ -309,11 +374,7 @@ func NewCgroupsBuilder(buf []byte, maxItems uint32, systemdEnabled uint32, gener
374
// reserve directory slots for maxItems before packed item data is appended.
375
func CgroupsBuilderMinBytes(maxItems uint32) (int, bool) {
376
minRequired := uint64(cgroupsRespHdr) + uint64(maxItems)*uint64(cgroupsDirEntry)
312
- maxInt := uint64(int(^uint(0) >> 1))
313
- if minRequired > maxInt {
314
- return 0, false
315
- }
316
- return int(minRequired), true
377
+ return checkedInt(minRequired)
378
}
379
380
// SetHeader updates the response header fields written by Finish().
@@ -333,7 +394,12 @@ func EstimateCgroupsMaxItems(bufSize int) uint32 {
394
}
395
396
minAlignedItem := Align8(cgroupsItemHdr + 2)
336
- return uint32((bufSize - cgroupsRespHdr) / (cgroupsDirEntry + minAlignedItem))
397
+ items := (bufSize - cgroupsRespHdr) / (cgroupsDirEntry + minAlignedItem)
398
+ items32, ok := checkedU32Int(items)
399
+ if !ok {
400
+ return ^uint32(0)
401
+ }
402
+ return items32
403
}
404
405
// Add adds one cgroup item. Handles offset bookkeeping, NUL termination,
@@ -343,13 +409,30 @@ func (b *CgroupsBuilder) Add(hash, options, enabled uint32, name, path []byte) e
409
return ErrOverflow
410
}
411
346
- // Align item start to 8 bytes.
347
- itemStart := Align8(b.dataOffset)
412
+ itemStart, ok := checkedAlign8(b.dataOffset)
413
+ if !ok {
414
+ return ErrOverflow
415
+ }
416
349
- // Item payload: 32-byte header + name + NUL + path + NUL.
350
- itemSize := cgroupsItemHdr + len(name) + 1 + len(path) + 1
417
+ nameSize, ok := checkedAddInt(len(name), 1)
418
+ if !ok {
419
+ return ErrOverflow
420
+ }
421
+ pathSize, ok := checkedAddInt(len(path), 1)
422
+ if !ok {
423
+ return ErrOverflow
424
+ }
425
+ itemSize, ok := checkedAddInt(cgroupsItemHdr, nameSize)
426
+ if !ok {
427
+ return ErrOverflow
428
+ }
429
+ itemSize, ok = checkedAddInt(itemSize, pathSize)
430
+ if !ok {
431
+ return ErrOverflow
432
+ }
433
352
- if itemStart+itemSize > len(b.buf) {
434
+ itemEnd, ok := checkedAddInt(itemStart, itemSize)
435
+ if !ok || itemEnd > len(b.buf) {
436
return ErrOverflow
437
}
438
@@ -358,8 +441,31 @@ func (b *CgroupsBuilder) Add(hash, options, enabled uint32, name, path []byte) e
441
clear(b.buf[b.dataOffset:itemStart])
442
}
443
361
- nameOffset := uint32(cgroupsItemHdr)
362
- pathOffset := uint32(cgroupsItemHdr) + uint32(len(name)) + 1
444
+ nameLen32, ok := checkedU32Int(len(name))
445
+ if !ok {
446
+ return ErrOverflow
447
+ }
448
+ pathLen32, ok := checkedU32Int(len(path))
449
+ if !ok {
450
+ return ErrOverflow
451
+ }
452
+ nameOffset32 := uint32(cgroupsItemHdr)
453
+ pathOffset, ok := checkedAddInt(cgroupsItemHdr, nameSize)
454
+ if !ok {
455
+ return ErrOverflow
456
+ }
457
+ pathOffset32, ok := checkedU32Int(pathOffset)
458
+ if !ok {
459
+ return ErrOverflow
460
+ }
461
+ itemStart32, ok := checkedU32Int(itemStart)
462
+ if !ok {
463
+ return ErrOverflow
464
+ }
465
+ itemSize32, ok := checkedU32Int(itemSize)
466
+ if !ok {
467
+ return ErrOverflow
468
+ }
469
470
// Write item header.
471
p := itemStart
@@ -368,26 +474,33 @@ func (b *CgroupsBuilder) Add(hash, options, enabled uint32, name, path []byte) e
474
ne.PutUint32(b.buf[p+4:p+8], hash)
475
ne.PutUint32(b.buf[p+8:p+12], options)
476
ne.PutUint32(b.buf[p+12:p+16], enabled)
371
- ne.PutUint32(b.buf[p+16:p+20], nameOffset)
372
- ne.PutUint32(b.buf[p+20:p+24], uint32(len(name)))
373
- ne.PutUint32(b.buf[p+24:p+28], pathOffset)
374
- ne.PutUint32(b.buf[p+28:p+32], uint32(len(path)))
477
+ ne.PutUint32(b.buf[p+16:p+20], nameOffset32)
478
+ ne.PutUint32(b.buf[p+20:p+24], nameLen32)
479
+ ne.PutUint32(b.buf[p+24:p+28], pathOffset32)
480
+ ne.PutUint32(b.buf[p+28:p+32], pathLen32)
481
482
// Write strings with NUL terminators.
377
- ns := p + int(nameOffset)
483
+ ns := p + cgroupsItemHdr
484
copy(b.buf[ns:], name)
485
b.buf[ns+len(name)] = 0
486
381
- ps := p + int(pathOffset)
487
+ ps := p + pathOffset
488
copy(b.buf[ps:], path)
489
b.buf[ps+len(path)] = 0
490
491
// Write directory entry (absolute offset stored temporarily).
386
- dirEntry := cgroupsRespHdr + int(b.itemCount)*cgroupsDirEntry
387
- ne.PutUint32(b.buf[dirEntry:dirEntry+4], uint32(itemStart))
388
- ne.PutUint32(b.buf[dirEntry+4:dirEntry+8], uint32(itemSize))
492
+ dirEntryOff, ok := checkedInt(uint64(b.itemCount) * uint64(cgroupsDirEntry))
493
+ if !ok {
494
+ return ErrOverflow
495
+ }
496
+ dirEntry, ok := checkedAddInt(cgroupsRespHdr, dirEntryOff)
497
+ if !ok {
498
+ return ErrOverflow
499
+ }
500
+ ne.PutUint32(b.buf[dirEntry:dirEntry+4], itemStart32)
501
+ ne.PutUint32(b.buf[dirEntry+4:dirEntry+8], itemSize32)
502
390
- b.dataOffset = itemStart + itemSize
503
+ b.dataOffset = itemEnd
504
b.itemCount++
505
return nil
506
}
@@ -407,25 +520,49 @@ func (b *CgroupsBuilder) Finish() int {
520
return cgroupsRespHdr
521
}
522
410
- // Where the decoder expects packed data to start.
411
- finalPackedStart := cgroupsRespHdr + int(b.itemCount)*cgroupsDirEntry
523
+ dirSize, ok := checkedInt(uint64(b.itemCount) * uint64(cgroupsDirEntry))
524
+ if !ok {
525
+ return 0
526
+ }
527
+ finalPackedStart, ok := checkedAddInt(cgroupsRespHdr, dirSize)
528
+ if !ok {
529
+ return 0
530
+ }
531
532
// Read the first directory entry to find where packed data begins.
414
- firstItemAbs := int(ne.Uint32(p[cgroupsRespHdr : cgroupsRespHdr+4]))
533
+ firstItemAbs32 := ne.Uint32(p[cgroupsRespHdr : cgroupsRespHdr+4])
534
+ firstItemAbs, ok := checkedInt(uint64(firstItemAbs32))
535
+ if !ok {
536
+ return 0
537
+ }
538
539
packedDataLen := b.dataOffset - firstItemAbs
540
541
if finalPackedStart < firstItemAbs {
542
+ packedDataEnd, ok := checkedAddInt(firstItemAbs, packedDataLen)
543
+ if !ok {
544
+ return 0
545
+ }
546
// Shift packed data left.
420
- copy(p[finalPackedStart:], p[firstItemAbs:firstItemAbs+packedDataLen])
547
+ copy(p[finalPackedStart:], p[firstItemAbs:packedDataEnd])
548
}
549
550
// Convert directory entries from absolute to relative offsets.
551
dirBase := cgroupsRespHdr
425
- for i := 0; i < int(b.itemCount); i++ {
426
- entry := dirBase + i*cgroupsDirEntry
552
+ for i := uint32(0); i < b.itemCount; i++ {
553
+ entryOff, ok := checkedInt(uint64(i) * uint64(cgroupsDirEntry))
554
+ if !ok {
555
+ return 0
556
+ }
557
+ entry, ok := checkedAddInt(dirBase, entryOff)
558
+ if !ok {
559
+ return 0
560
+ }
561
absOff := ne.Uint32(p[entry : entry+4])
428
- relOff := absOff - uint32(firstItemAbs)
562
+ if absOff < firstItemAbs32 {
563
+ return 0
564
+ }
565
+ relOff := absOff - firstItemAbs32
566
ne.PutUint32(p[entry:entry+4], relOff)
567
// length stays the same.
568
}
@@ -438,7 +575,11 @@ func (b *CgroupsBuilder) Finish() int {
575
ne.PutUint32(p[12:16], 0)
576
ne.PutUint64(p[16:24], b.generation)
577
441
- return finalPackedStart + packedDataLen
578
+ total, ok := checkedAddInt(finalPackedStart, packedDataLen)
579
+ if !ok {
580
+ return 0
581
+ }
582
+ return total
583
}
584
585
// DispatchCgroupsSnapshot decodes request, builds response via handler.
src/go/pkg/netipc/protocol/frame.go
+128
-36
@@ -48,6 +48,8 @@ const (
48
MethodIncrement uint16 = 1
49
MethodCgroupsSnapshot uint16 = 2
50
MethodStringReverse uint16 = 3
51
+ MethodCgroupsLookup uint16 = 4
52
+ MethodAppsLookup uint16 = 5
53
54
// Profile bits.
55
ProfileBaseline uint32 = 0x01
@@ -62,16 +64,12 @@ const (
64
// (1 MiB) to prevent excessive memory allocation from a compromised peer.
65
MaxPayloadCap uint32 = 1024 * 1024
66
65
- // Alignment for batch items and cgroups items.
67
+ // Alignment for batch items and typed codec items.
68
Alignment = 8
69
70
// Payload sizes.
69
- helloSize = 44
70
- helloAckSize = 48
71
- cgroupsReqSize = 4
72
- cgroupsRespHdr = 24
73
- cgroupsDirEntry = 8
74
- cgroupsItemHdr = 32
71
+ helloSize = 44
72
+ helloAckSize = 48
73
)
74
75
var ne = binary.NativeEndian
@@ -265,8 +263,14 @@ func BatchDirEncode(entries []BatchEntry, buf []byte) int {
263
// BatchDirDecode decodes itemCount directory entries from buf. Validates
264
// alignment and that each entry falls within packedAreaLen.
265
func BatchDirDecode(buf []byte, itemCount uint32, packedAreaLen uint32) ([]BatchEntry, error) {
268
- count := int(itemCount)
269
- dirSize := count * 8
266
+ count, ok := checkedInt(uint64(itemCount))
267
+ if !ok {
268
+ return nil, ErrBadItemCount
269
+ }
270
+ dirSize, ok := checkedMulInt(count, 8)
271
+ if !ok {
272
+ return nil, ErrBadItemCount
273
+ }
274
if len(buf) < dirSize {
275
return nil, ErrTruncated
276
}
@@ -277,7 +281,7 @@ func BatchDirDecode(buf []byte, itemCount uint32, packedAreaLen uint32) ([]Batch
281
off := ne.Uint32(buf[base : base+4])
282
length := ne.Uint32(buf[base+4 : base+8])
283
280
- if int(off)%Alignment != 0 {
284
+ if off%uint32(Alignment) != 0 {
285
return nil, ErrBadAlignment
286
}
287
if uint64(off)+uint64(length) > uint64(packedAreaLen) {
@@ -291,8 +295,14 @@ func BatchDirDecode(buf []byte, itemCount uint32, packedAreaLen uint32) ([]Batch
295
// BatchDirValidate validates the batch directory without allocating.
296
// Checks alignment and that each entry falls within packedAreaLen.
297
func BatchDirValidate(buf []byte, itemCount uint32, packedAreaLen uint32) error {
294
- count := int(itemCount)
295
- dirSize := count * 8
298
+ count, ok := checkedInt(uint64(itemCount))
299
+ if !ok {
300
+ return ErrBadItemCount
301
+ }
302
+ dirSize, ok := checkedMulInt(count, 8)
303
+ if !ok {
304
+ return ErrBadItemCount
305
+ }
306
if len(buf) < dirSize {
307
return ErrTruncated
308
}
@@ -300,7 +310,7 @@ func BatchDirValidate(buf []byte, itemCount uint32, packedAreaLen uint32) error
310
base := i * 8
311
off := ne.Uint32(buf[base : base+4])
312
length := ne.Uint32(buf[base+4 : base+8])
303
- if int(off)%Alignment != 0 {
313
+ if off%uint32(Alignment) != 0 {
314
return ErrBadAlignment
315
}
316
if uint64(off)+uint64(length) > uint64(packedAreaLen) {
@@ -317,30 +327,55 @@ func BatchItemGet(payload []byte, itemCount uint32, index uint32) ([]byte, error
327
return nil, ErrOutOfBounds
328
}
329
320
- dirSize := int(itemCount) * 8
321
- dirAligned := Align8(dirSize)
330
+ dirSize, ok := checkedInt(uint64(itemCount) * 8)
331
+ if !ok {
332
+ return nil, ErrBadItemCount
333
+ }
334
+ dirAligned, ok := checkedAlign8(dirSize)
335
+ if !ok {
336
+ return nil, ErrBadItemCount
337
+ }
338
339
if len(payload) < dirAligned {
340
return nil, ErrTruncated
341
}
342
327
- idx := int(index)
328
- base := idx * 8
329
- off := ne.Uint32(payload[base : base+4])
330
- length := ne.Uint32(payload[base+4 : base+8])
343
+ idx, ok := checkedInt(uint64(index))
344
+ if !ok {
345
+ return nil, ErrOutOfBounds
346
+ }
347
+ base, ok := checkedMulInt(idx, 8)
348
+ if !ok {
349
+ return nil, ErrOutOfBounds
350
+ }
351
+ off, err := checkedWireU32Int(payload, base)
352
+ if err != nil {
353
+ return nil, err
354
+ }
355
+ length, err := checkedWireU32Int(payload, base+4)
356
+ if err != nil {
357
+ return nil, err
358
+ }
359
360
packedAreaStart := dirAligned
361
packedAreaLen := len(payload) - packedAreaStart
362
335
- if int(off)%Alignment != 0 {
363
+ if off%Alignment != 0 {
364
return nil, ErrBadAlignment
365
}
338
- if uint64(off)+uint64(length) > uint64(packedAreaLen) {
366
+ relEnd, ok := checkedAddInt(off, length)
367
+ if !ok || relEnd > packedAreaLen {
368
return nil, ErrOutOfBounds
369
}
370
342
- start := packedAreaStart + int(off)
343
- end := start + int(length)
371
+ start, ok := checkedAddInt(packedAreaStart, off)
372
+ if !ok {
373
+ return nil, ErrOutOfBounds
374
+ }
375
+ end, ok := checkedAddInt(start, length)
376
+ if !ok {
377
+ return nil, ErrOutOfBounds
378
+ }
379
return payload[start:end], nil
380
}
381
@@ -364,7 +399,19 @@ func (b *BatchBuilder) Reset(buf []byte, maxItems uint32) {
399
b.buf = buf
400
b.itemCount = 0
401
b.maxItems = maxItems
367
- b.dirEnd = Align8(int(maxItems) * 8)
402
+ dirSize, ok := checkedInt(uint64(maxItems) * 8)
403
+ if !ok {
404
+ b.dirEnd = maxIntValue()
405
+ b.dataOffset = 0
406
+ return
407
+ }
408
+ dirEnd, ok := checkedAlign8(dirSize)
409
+ if !ok {
410
+ b.dirEnd = maxIntValue()
411
+ b.dataOffset = 0
412
+ return
413
+ }
414
+ b.dirEnd = dirEnd
415
b.dataOffset = 0
416
}
417
@@ -382,28 +429,55 @@ func (b *BatchBuilder) Add(item []byte) error {
429
return ErrOverflow
430
}
431
385
- alignedOff := Align8(b.dataOffset)
386
- absPos := b.dirEnd + alignedOff
432
+ alignedOff, ok := checkedAlign8(b.dataOffset)
433
+ if !ok {
434
+ return ErrOverflow
435
+ }
436
+ absPos, ok := checkedAddInt(b.dirEnd, alignedOff)
437
+ if !ok {
438
+ return ErrOverflow
439
+ }
440
388
- if absPos+len(item) > len(b.buf) {
441
+ itemEnd, ok := checkedAddInt(absPos, len(item))
442
+ if !ok || itemEnd > len(b.buf) {
443
return ErrOverflow
444
}
445
446
// Zero alignment padding.
447
if alignedOff > b.dataOffset {
394
- padStart := b.dirEnd + b.dataOffset
395
- padEnd := b.dirEnd + alignedOff
448
+ padStart, ok := checkedAddInt(b.dirEnd, b.dataOffset)
449
+ if !ok {
450
+ return ErrOverflow
451
+ }
452
+ padEnd, ok := checkedAddInt(b.dirEnd, alignedOff)
453
+ if !ok {
454
+ return ErrOverflow
455
+ }
456
clear(b.buf[padStart:padEnd])
457
}
458
459
copy(b.buf[absPos:], item)
460
461
// Write directory entry.
402
- idx := int(b.itemCount) * 8
403
- ne.PutUint32(b.buf[idx:idx+4], uint32(alignedOff))
404
- ne.PutUint32(b.buf[idx+4:idx+8], uint32(len(item)))
462
+ idx, ok := checkedInt(uint64(b.itemCount) * 8)
463
+ if !ok {
464
+ return ErrOverflow
465
+ }
466
+ alignedOff32, ok := checkedU32Int(alignedOff)
467
+ if !ok {
468
+ return ErrOverflow
469
+ }
470
+ itemLen32, ok := checkedU32Int(len(item))
471
+ if !ok {
472
+ return ErrOverflow
473
+ }
474
+ ne.PutUint32(b.buf[idx:idx+4], alignedOff32)
475
+ ne.PutUint32(b.buf[idx+4:idx+8], itemLen32)
476
406
- b.dataOffset = alignedOff + len(item)
477
+ b.dataOffset, ok = checkedAddInt(alignedOff, len(item))
478
+ if !ok {
479
+ return ErrOverflow
480
+ }
481
b.itemCount++
482
return nil
483
}
@@ -412,14 +486,32 @@ func (b *BatchBuilder) Add(item []byte) error {
486
// Compacts if fewer items were added than maxItems.
487
func (b *BatchBuilder) Finish() (int, uint32) {
488
count := b.itemCount
415
- finalDirAligned := Align8(int(count) * 8)
489
+ dirSize, ok := checkedInt(uint64(count) * 8)
490
+ if !ok {
491
+ return 0, count
492
+ }
493
+ finalDirAligned, ok := checkedAlign8(dirSize)
494
+ if !ok {
495
+ return 0, count
496
+ }
497
498
if finalDirAligned < b.dirEnd && b.dataOffset > 0 {
499
+ dataEnd, ok := checkedAddInt(b.dirEnd, b.dataOffset)
500
+ if !ok {
501
+ return 0, count
502
+ }
503
// Shift packed data left.
419
- copy(b.buf[finalDirAligned:], b.buf[b.dirEnd:b.dirEnd+b.dataOffset])
504
+ copy(b.buf[finalDirAligned:], b.buf[b.dirEnd:dataEnd])
505
}
506
422
- total := finalDirAligned + Align8(b.dataOffset)
507
+ alignedData, ok := checkedAlign8(b.dataOffset)
508
+ if !ok {
509
+ return 0, count
510
+ }
511
+ total, ok := checkedAddInt(finalDirAligned, alignedData)
512
+ if !ok {
513
+ return 0, count
514
+ }
515
return total, count
516
}
517
src/go/pkg/netipc/protocol/fuzz_test.go
+146
@@ -213,6 +213,152 @@ func FuzzDecodeCgroupsResponse(f *testing.F) {
213
})
214
}
215
216
+func FuzzDecodeCgroupsLookupRequest(f *testing.F) {
217
+ var seed [128]byte
218
+ n, err := EncodeCgroupsLookupRequest([][]byte{[]byte("/a"), []byte("/b/c")}, seed[:])
219
+ if err == nil {
220
+ f.Add(seed[:n])
221
+ }
222
+ f.Add([]byte{})
223
+ f.Add(make([]byte, CgroupsLookupReqHdr-1))
224
+ f.Add(make([]byte, CgroupsLookupReqHdr))
225
+
226
+ f.Fuzz(func(t *testing.T, data []byte) {
227
+ view, err := DecodeCgroupsLookupRequest(data)
228
+ if err != nil {
229
+ return
230
+ }
231
+ for i := uint32(0); i < view.ItemCount; i++ {
232
+ item, ierr := view.Item(i)
233
+ if ierr == nil {
234
+ _ = item.Bytes()
235
+ _ = item.String()
236
+ }
237
+ }
238
+ _, _ = view.Item(view.ItemCount)
239
+ })
240
+}
241
+
242
+func FuzzDecodeCgroupsLookupResponse(f *testing.F) {
243
+ var empty [128]byte
244
+ eb := NewCgroupsLookupBuilder(empty[:], 0, 0)
245
+ f.Add(empty[:eb.Finish()])
246
+
247
+ var seed [512]byte
248
+ builder := NewCgroupsLookupBuilder(seed[:], 1, 1)
249
+ if err := builder.Add(
250
+ CgroupLookupKnown,
251
+ OrchestratorK8s,
252
+ []byte("/a"),
253
+ []byte("pod-a"),
254
+ []struct{ Key, Value []byte }{{Key: []byte("namespace"), Value: []byte("default")}},
255
+ ); err == nil {
256
+ f.Add(seed[:builder.Finish()])
257
+ }
258
+ f.Add([]byte{})
259
+ f.Add(make([]byte, CgroupsLookupRespHdr-1))
260
+ f.Add(make([]byte, CgroupsLookupRespHdr))
261
+
262
+ f.Fuzz(func(t *testing.T, data []byte) {
263
+ view, err := DecodeCgroupsLookupResponse(data)
264
+ if err != nil {
265
+ return
266
+ }
267
+ for i := uint32(0); i < view.ItemCount; i++ {
268
+ item, ierr := view.Item(i)
269
+ if ierr != nil {
270
+ continue
271
+ }
272
+ _ = item.Path.Bytes()
273
+ _ = item.Name.String()
274
+ for j := uint32(0); j < uint32(item.LabelCount); j++ {
275
+ label, lerr := item.Label(j)
276
+ if lerr == nil {
277
+ _ = label.Key.String()
278
+ _ = label.Value.String()
279
+ }
280
+ }
281
+ }
282
+ _, _ = view.Item(view.ItemCount)
283
+ })
284
+}
285
+
286
+func FuzzDecodeAppsLookupRequest(f *testing.F) {
287
+ var seed [128]byte
288
+ n, err := EncodeAppsLookupRequest([]uint32{0, 1234}, seed[:])
289
+ if err == nil {
290
+ f.Add(seed[:n])
291
+ }
292
+ f.Add([]byte{})
293
+ f.Add(make([]byte, AppsLookupReqHdr-1))
294
+ f.Add(make([]byte, AppsLookupReqHdr))
295
+
296
+ f.Fuzz(func(t *testing.T, data []byte) {
297
+ view, err := DecodeAppsLookupRequest(data)
298
+ if err != nil {
299
+ return
300
+ }
301
+ for i := uint32(0); i < view.ItemCount; i++ {
302
+ pid, ierr := view.Item(i)
303
+ if ierr == nil {
304
+ _ = pid
305
+ }
306
+ }
307
+ _, _ = view.Item(view.ItemCount)
308
+ })
309
+}
310
+
311
+func FuzzDecodeAppsLookupResponse(f *testing.F) {
312
+ var empty [128]byte
313
+ eb := NewAppsLookupBuilder(empty[:], 0, 0)
314
+ f.Add(empty[:eb.Finish()])
315
+
316
+ var seed [1024]byte
317
+ builder := NewAppsLookupBuilder(seed[:], 1, 1)
318
+ if err := builder.Add(
319
+ PidLookupKnown,
320
+ AppsCgroupKnown,
321
+ OrchestratorDocker,
322
+ 1234,
323
+ 1,
324
+ 1000,
325
+ 42,
326
+ []byte("nginx"),
327
+ []byte("/docker/abc"),
328
+ []byte("container-a"),
329
+ []struct{ Key, Value []byte }{{Key: []byte("image"), Value: []byte("nginx:latest")}},
330
+ ); err == nil {
331
+ f.Add(seed[:builder.Finish()])
332
+ }
333
+ f.Add([]byte{})
334
+ f.Add(make([]byte, AppsLookupRespHdr-1))
335
+ f.Add(make([]byte, AppsLookupRespHdr))
336
+
337
+ f.Fuzz(func(t *testing.T, data []byte) {
338
+ view, err := DecodeAppsLookupResponse(data)
339
+ if err != nil {
340
+ return
341
+ }
342
+ for i := uint32(0); i < view.ItemCount; i++ {
343
+ item, ierr := view.Item(i)
344
+ if ierr != nil {
345
+ continue
346
+ }
347
+ _ = item.Comm.String()
348
+ _ = item.CgroupPath.Bytes()
349
+ _ = item.CgroupName.String()
350
+ for j := uint32(0); j < uint32(item.LabelCount); j++ {
351
+ label, lerr := item.Label(j)
352
+ if lerr == nil {
353
+ _ = label.Key.String()
354
+ _ = label.Value.String()
355
+ }
356
+ }
357
+ }
358
+ _, _ = view.Item(view.ItemCount)
359
+ })
360
+}
361
+
362
func FuzzBatchDirDecode(f *testing.F) {
363
// Seed: valid 2-entry directory.
364
var seed [16]byte
src/go/pkg/netipc/protocol/lookup.go
new
+1
@@ -0,0 +1 @@
1
+package protocol
src/go/pkg/netipc/protocol/lookup_common.go
new
+492
@@ -0,0 +1,492 @@
1
+package protocol
2
+
3
+import "bytes"
4
+
5
+const (
6
+ OrchestratorUnknown uint16 = 0
7
+ OrchestratorSystemd uint16 = 1
8
+ OrchestratorDocker uint16 = 2
9
+ OrchestratorK8s uint16 = 3
10
+ OrchestratorKvm uint16 = 4
11
+ OrchestratorLxc uint16 = 5
12
+ OrchestratorPodman uint16 = 6
13
+ OrchestratorNspawn uint16 = 7
14
+
15
+ LookupDirEntrySize = 8
16
+ LookupLabelEntrySize = 16
17
+)
18
+
19
+// LookupLabelView represents a key-value label pair view in the lookup wire format.
20
+type LookupLabelView struct {
21
+ Key CStringView
22
+ Value CStringView
23
+}
24
+
25
+func invalidSourceString(data []byte, requireNonEmpty bool) bool {
26
+ return (requireNonEmpty && len(data) == 0) || bytes.IndexByte(data, 0) >= 0
27
+}
28
+
29
+// maxIntValue returns the maximum value representable by int on this platform.
30
+func maxIntValue() int {
31
+ return int(^uint(0) >> 1)
32
+}
33
+
34
+func checkedU32Int(value int) (uint32, bool) {
35
+ if value < 0 || uint64(value) > uint64(^uint32(0)) {
36
+ return 0, false
37
+ }
38
+ return uint32(value), true
39
+}
40
+
41
+func checkedU16Int(value int) (uint16, bool) {
42
+ if value < 0 || value > int(^uint16(0)) {
43
+ return 0, false
44
+ }
45
+ return uint16(value), true
46
+}
47
+
48
+func checkedWireU32Int(buf []byte, off int) (int, error) {
49
+ value, ok := checkedInt(uint64(ne.Uint32(buf[off : off+4])))
50
+ if !ok {
51
+ return 0, ErrOutOfBounds
52
+ }
53
+ return value, nil
54
+}
55
+
56
+func lookupDirEntry(buf []byte, base int) (int, int, error) {
57
+ off, err := checkedWireU32Int(buf, base)
58
+ if err != nil {
59
+ return 0, 0, err
60
+ }
61
+ length, err := checkedWireU32Int(buf, base+4)
62
+ if err != nil {
63
+ return 0, 0, err
64
+ }
65
+ return off, length, nil
66
+}
67
+
68
+func lookupPayloadSlice(buf []byte, start int, off int, length int) ([]byte, error) {
69
+ abs, ok := checkedAddInt(start, off)
70
+ if !ok {
71
+ return nil, ErrOutOfBounds
72
+ }
73
+ end, ok := checkedAddInt(abs, length)
74
+ if !ok || end > len(buf) {
75
+ return nil, ErrOutOfBounds
76
+ }
77
+ return buf[abs:end], nil
78
+}
79
+
80
+func lookupBuilderDataOffset(hdrSize int, maxItems uint32) (int, bool) {
81
+ dirSize, ok := checkedInt(uint64(maxItems) * uint64(LookupDirEntrySize))
82
+ if !ok {
83
+ return 0, false
84
+ }
85
+ return checkedAddInt(hdrSize, dirSize)
86
+}
87
+
88
+func lookupDirOffset(hdrSize int, index uint32) (int, bool) {
89
+ dirOff, ok := checkedInt(uint64(index) * uint64(LookupDirEntrySize))
90
+ if !ok {
91
+ return 0, false
92
+ }
93
+ return checkedAddInt(hdrSize, dirOff)
94
+}
95
+
96
+func checkedInt(value uint64) (int, bool) {
97
+ maxInt := uint64(maxIntValue()) // #nosec G115 -- maxIntValue is non-negative and intentionally widened for the bounds check.
98
+ if value > maxInt {
99
+ return 0, false
100
+ }
101
+ return int(value), true // #nosec G115 -- value is bounded by maxInt above.
102
+}
103
+
104
+func checkedAddInt(a, b int) (int, bool) {
105
+ if a < 0 || b < 0 {
106
+ return 0, false
107
+ }
108
+ maxInt := maxIntValue()
109
+ if a > maxInt-b {
110
+ return 0, false
111
+ }
112
+ return a + b, true
113
+}
114
+
115
+func checkedMulInt(a, b int) (int, bool) {
116
+ if a < 0 || b < 0 {
117
+ return 0, false
118
+ }
119
+ maxInt := maxIntValue()
120
+ if a != 0 && b > maxInt/a {
121
+ return 0, false
122
+ }
123
+ return a * b, true
124
+}
125
+
126
+func checkedAlign8(v int) (int, bool) {
127
+ if v < 0 {
128
+ return 0, false
129
+ }
130
+ maxInt := maxIntValue()
131
+ if v > maxInt-7 {
132
+ return 0, false
133
+ }
134
+ return Align8(v), true
135
+}
136
+
137
+func validateLookupDir(buf []byte, dirStart int, itemCount uint32, packedAreaLen int, minLen int, exactLen int) error {
138
+ var minLen32 uint32
139
+ var exactLen32 uint32
140
+ if minLen >= 0 {
141
+ converted, ok := checkedU32Int(minLen)
142
+ if !ok {
143
+ return ErrBadLayout
144
+ }
145
+ minLen32 = converted
146
+ }
147
+ if exactLen >= 0 {
148
+ converted, ok := checkedU32Int(exactLen)
149
+ if !ok {
150
+ return ErrBadLayout
151
+ }
152
+ exactLen32 = converted
153
+ }
154
+
155
+ dirSize, ok := checkedInt(uint64(itemCount) * uint64(LookupDirEntrySize))
156
+ if !ok {
157
+ return ErrBadItemCount
158
+ }
159
+ dirEnd, ok := checkedAddInt(dirStart, dirSize)
160
+ if !ok {
161
+ return ErrBadItemCount
162
+ }
163
+ if dirEnd > len(buf) {
164
+ return ErrTruncated
165
+ }
166
+
167
+ prevEnd := 0
168
+ for i := range itemCount {
169
+ base, ok := lookupDirOffset(dirStart, i)
170
+ if !ok {
171
+ return ErrBadItemCount
172
+ }
173
+ off, length, err := lookupDirEntry(buf, base)
174
+ if err != nil {
175
+ return err
176
+ }
177
+ if off%Alignment != 0 {
178
+ return ErrBadAlignment
179
+ }
180
+ length32, ok := checkedU32Int(length)
181
+ if !ok {
182
+ return ErrOutOfBounds
183
+ }
184
+ if exactLen >= 0 {
185
+ if length32 != exactLen32 {
186
+ return ErrBadLayout
187
+ }
188
+ } else if length32 < minLen32 {
189
+ return ErrBadLayout
190
+ }
191
+ end, ok := checkedAddInt(off, length)
192
+ if !ok || end > packedAreaLen {
193
+ return ErrOutOfBounds
194
+ }
195
+ if i > 0 && off < prevEnd {
196
+ return ErrBadLayout
197
+ }
198
+ prevEnd = end
199
+ }
200
+ return nil
201
+}
202
+
203
+func lookupString(item []byte, hdrSize int, off int, length int) (CStringView, int, error) {
204
+ if off < hdrSize {
205
+ return CStringView{}, 0, ErrOutOfBounds
206
+ }
207
+ nul, ok := checkedAddInt(off, length)
208
+ if !ok || nul >= len(item) {
209
+ return CStringView{}, 0, ErrOutOfBounds
210
+ }
211
+ if item[nul] != 0 {
212
+ return CStringView{}, 0, ErrMissingNul
213
+ }
214
+ if bytes.Contains(item[off:nul], []byte{0}) {
215
+ return CStringView{}, 0, ErrBadLayout
216
+ }
217
+ length32, ok := checkedU32Int(length)
218
+ if !ok {
219
+ return CStringView{}, 0, ErrOutOfBounds
220
+ }
221
+ return NewCStringView(item[off:nul+1], length32), nul + 1, nil
222
+}
223
+
224
+func lookupEmptyString(item []byte, hdrSize int, off int) (CStringView, int, error) {
225
+ if off < hdrSize || off >= len(item) {
226
+ return CStringView{}, 0, ErrOutOfBounds
227
+ }
228
+ if item[off] != 0 {
229
+ return CStringView{}, 0, ErrMissingNul
230
+ }
231
+ return NewCStringView(item[off:off+1], 0), off + 1, nil
232
+}
233
+
234
+func overlap(aStart, aEnd, bStart, bEnd int) bool {
235
+ return aStart < bEnd && bStart < aEnd
236
+}
237
+
238
+func validateLabels(item []byte, hdrSize int, labelCount uint16, fixedEnd int) (int, error) {
239
+ if labelCount == 0 {
240
+ if fixedEnd != len(item) {
241
+ return 0, ErrBadLayout
242
+ }
243
+ return fixedEnd, nil
244
+ }
245
+
246
+ tableStart, ok := checkedAlign8(fixedEnd)
247
+ if !ok {
248
+ return 0, ErrOutOfBounds
249
+ }
250
+ if tableStart > len(item) {
251
+ return 0, ErrOutOfBounds
252
+ }
253
+ for _, b := range item[fixedEnd:tableStart] {
254
+ if b != 0 {
255
+ return 0, ErrBadLayout
256
+ }
257
+ }
258
+
259
+ tableBytes, ok := checkedInt(uint64(labelCount) * uint64(LookupLabelEntrySize))
260
+ if !ok {
261
+ return 0, ErrOutOfBounds
262
+ }
263
+ expected, ok := checkedAddInt(tableStart, tableBytes)
264
+ if !ok || expected > len(item) {
265
+ return 0, ErrOutOfBounds
266
+ }
267
+
268
+ for i := range labelCount {
269
+ entryRel, ok := checkedInt(uint64(i) * uint64(LookupLabelEntrySize))
270
+ if !ok {
271
+ return 0, ErrOutOfBounds
272
+ }
273
+ base, ok := checkedAddInt(tableStart, entryRel)
274
+ if !ok {
275
+ return 0, ErrOutOfBounds
276
+ }
277
+ keyOff, err := checkedWireU32Int(item, base)
278
+ if err != nil {
279
+ return 0, err
280
+ }
281
+ keyLen, err := checkedWireU32Int(item, base+4)
282
+ if err != nil {
283
+ return 0, err
284
+ }
285
+ valueOff, err := checkedWireU32Int(item, base+8)
286
+ if err != nil {
287
+ return 0, err
288
+ }
289
+ valueLen, err := checkedWireU32Int(item, base+12)
290
+ if err != nil {
291
+ return 0, err
292
+ }
293
+ if keyLen == 0 || keyOff != expected {
294
+ return 0, ErrBadLayout
295
+ }
296
+ _, keyEnd, err := lookupString(item, hdrSize, keyOff, keyLen)
297
+ if err != nil {
298
+ return 0, err
299
+ }
300
+ expected = keyEnd
301
+ if valueOff != expected {
302
+ return 0, ErrBadLayout
303
+ }
304
+ _, valueEnd, err := lookupString(item, hdrSize, valueOff, valueLen)
305
+ if err != nil {
306
+ return 0, err
307
+ }
308
+ expected = valueEnd
309
+ }
310
+ if expected != len(item) {
311
+ return 0, ErrBadLayout
312
+ }
313
+ return tableStart, nil
314
+}
315
+
316
+func lookupLabelAt(item []byte, hdrSize int, labelCount uint16, tableOffset int, index uint32) (LookupLabelView, error) {
317
+ if index >= uint32(labelCount) {
318
+ return LookupLabelView{}, ErrOutOfBounds
319
+ }
320
+ entryRel, ok := checkedInt(uint64(index) * uint64(LookupLabelEntrySize))
321
+ if !ok {
322
+ return LookupLabelView{}, ErrOutOfBounds
323
+ }
324
+ base, ok := checkedAddInt(tableOffset, entryRel)
325
+ if !ok {
326
+ return LookupLabelView{}, ErrOutOfBounds
327
+ }
328
+ keyOff, err := checkedWireU32Int(item, base)
329
+ if err != nil {
330
+ return LookupLabelView{}, err
331
+ }
332
+ keyLen, err := checkedWireU32Int(item, base+4)
333
+ if err != nil {
334
+ return LookupLabelView{}, err
335
+ }
336
+ valueOff, err := checkedWireU32Int(item, base+8)
337
+ if err != nil {
338
+ return LookupLabelView{}, err
339
+ }
340
+ valueLen, err := checkedWireU32Int(item, base+12)
341
+ if err != nil {
342
+ return LookupLabelView{}, err
343
+ }
344
+ key, _, err := lookupString(item, hdrSize, keyOff, keyLen)
345
+ if err != nil {
346
+ return LookupLabelView{}, err
347
+ }
348
+ value, _, err := lookupString(item, hdrSize, valueOff, valueLen)
349
+ if err != nil {
350
+ return LookupLabelView{}, err
351
+ }
352
+ return LookupLabelView{Key: key, Value: value}, nil
353
+}
354
+
355
+func writeLookupLabels(item []byte, tableStart, tableBytes int, labels []struct{ Key, Value []byte }) (int, error) {
356
+ next, ok := checkedAddInt(tableStart, tableBytes)
357
+ if !ok {
358
+ return 0, ErrOverflow
359
+ }
360
+ for i, label := range labels {
361
+ keyOff32, ok := checkedU32Int(next)
362
+ if !ok {
363
+ return 0, ErrOverflow
364
+ }
365
+ keyLen32, ok := checkedU32Int(len(label.Key))
366
+ if !ok {
367
+ return 0, ErrOverflow
368
+ }
369
+ valueOff, ok := checkedAddInt(next, len(label.Key))
370
+ if ok {
371
+ valueOff, ok = checkedAddInt(valueOff, 1)
372
+ }
373
+ if !ok {
374
+ return 0, ErrOverflow
375
+ }
376
+ valueOff32, ok := checkedU32Int(valueOff)
377
+ if !ok {
378
+ return 0, ErrOverflow
379
+ }
380
+ valueLen32, ok := checkedU32Int(len(label.Value))
381
+ if !ok {
382
+ return 0, ErrOverflow
383
+ }
384
+ entryRel, ok := checkedMulInt(i, LookupLabelEntrySize)
385
+ if !ok {
386
+ return 0, ErrOverflow
387
+ }
388
+ entry, ok := checkedAddInt(tableStart, entryRel)
389
+ if !ok {
390
+ return 0, ErrOverflow
391
+ }
392
+ ne.PutUint32(item[entry:entry+4], keyOff32)
393
+ ne.PutUint32(item[entry+4:entry+8], keyLen32)
394
+ ne.PutUint32(item[entry+8:entry+12], valueOff32)
395
+ ne.PutUint32(item[entry+12:entry+16], valueLen32)
396
+ copy(item[next:], label.Key)
397
+ item[next+len(label.Key)] = 0
398
+ next = valueOff
399
+ copy(item[next:], label.Value)
400
+ item[next+len(label.Value)] = 0
401
+ next, ok = checkedAddInt(next, len(label.Value))
402
+ if ok {
403
+ next, ok = checkedAddInt(next, 1)
404
+ }
405
+ if !ok {
406
+ return 0, ErrOverflow
407
+ }
408
+ }
409
+ return next, nil
410
+}
411
+
412
+func labelLayoutGo(fixedEnd int, labels []struct{ Key, Value []byte }) (int, int, int, error) {
413
+ if len(labels) == 0 {
414
+ return fixedEnd, 0, fixedEnd, nil
415
+ }
416
+ tableStart, ok := checkedAlign8(fixedEnd)
417
+ if !ok {
418
+ return 0, 0, 0, ErrOverflow
419
+ }
420
+ tableBytes, ok := checkedMulInt(len(labels), LookupLabelEntrySize)
421
+ if !ok {
422
+ return 0, 0, 0, ErrOverflow
423
+ }
424
+ itemSize, ok := checkedAddInt(tableStart, tableBytes)
425
+ if !ok {
426
+ return 0, 0, 0, ErrOverflow
427
+ }
428
+ for _, label := range labels {
429
+ if invalidSourceString(label.Key, true) || invalidSourceString(label.Value, false) {
430
+ return 0, 0, 0, ErrBadLayout
431
+ }
432
+ keySize, ok := checkedAddInt(len(label.Key), 1)
433
+ if ok {
434
+ valueSize, okValue := checkedAddInt(len(label.Value), 1)
435
+ if okValue {
436
+ keySize, ok = checkedAddInt(keySize, valueSize)
437
+ } else {
438
+ ok = false
439
+ }
440
+ }
441
+ if ok {
442
+ itemSize, ok = checkedAddInt(itemSize, keySize)
443
+ }
444
+ if !ok {
445
+ return 0, 0, 0, ErrOverflow
446
+ }
447
+ }
448
+ return tableStart, tableBytes, itemSize, nil
449
+}
450
+
451
+func finishLookupResponse(buf []byte, hdrSize int, itemCount uint32, dataOffset int, generation uint64) int {
452
+ ne.PutUint16(buf[0:2], 1)
453
+ ne.PutUint16(buf[2:4], 0)
454
+ ne.PutUint32(buf[4:8], itemCount)
455
+ ne.PutUint64(buf[8:16], generation)
456
+ if itemCount == 0 {
457
+ return hdrSize
458
+ }
459
+ dirSize, ok := checkedInt(uint64(itemCount) * uint64(LookupDirEntrySize))
460
+ if !ok {
461
+ return 0
462
+ }
463
+ count := int(itemCount)
464
+ finalPackedStart, ok := checkedAddInt(hdrSize, dirSize)
465
+ if !ok {
466
+ return 0
467
+ }
468
+ firstItemAbs, ok := checkedInt(uint64(ne.Uint32(buf[hdrSize : hdrSize+4])))
469
+ if !ok {
470
+ return 0
471
+ }
472
+ if dataOffset < firstItemAbs {
473
+ return 0
474
+ }
475
+ packedDataLen := dataOffset - firstItemAbs
476
+ if finalPackedStart < firstItemAbs {
477
+ copy(buf[finalPackedStart:], buf[firstItemAbs:firstItemAbs+packedDataLen])
478
+ }
479
+ for i := range count {
480
+ entry := hdrSize + i*LookupDirEntrySize
481
+ abs, ok := checkedInt(uint64(ne.Uint32(buf[entry : entry+4])))
482
+ if !ok || abs < firstItemAbs {
483
+ return 0
484
+ }
485
+ rel, ok := checkedU32Int(abs - firstItemAbs)
486
+ if !ok {
487
+ return 0
488
+ }
489
+ ne.PutUint32(buf[entry:entry+4], rel)
490
+ }
491
+ return finalPackedStart + packedDataLen
492
+}
src/go/pkg/netipc/protocol/lookup_test.go
new
+1555
@@ -0,0 +1,1555 @@
1
+package protocol
2
+
3
+import "testing"
4
+
5
+func labels(items ...struct{ Key, Value []byte }) []struct{ Key, Value []byte } {
6
+ return items
7
+}
8
+
9
+func TestCgroupsLookupRoundTrip(t *testing.T) {
10
+ var req [256]byte
11
+ n, err := EncodeCgroupsLookupRequest([][]byte{
12
+ []byte("/sys/fs/cgroup/a"),
13
+ []byte("/system.slice/docker-abc.scope"),
14
+ }, req[:])
15
+ if err != nil {
16
+ t.Fatalf("encode request: %v", err)
17
+ }
18
+ reqView, err := DecodeCgroupsLookupRequest(req[:n])
19
+ if err != nil {
20
+ t.Fatalf("decode request: %v", err)
21
+ }
22
+ if reqView.ItemCount != 2 {
23
+ t.Fatalf("item count = %d, want 2", reqView.ItemCount)
24
+ }
25
+ item0, err := reqView.Item(0)
26
+ if err != nil || item0.String() != "/sys/fs/cgroup/a" {
27
+ t.Fatalf("item 0 = %q, err=%v", item0.String(), err)
28
+ }
29
+
30
+ var resp [1024]byte
31
+ builder := NewCgroupsLookupBuilder(resp[:], 2, 123)
32
+ if err := builder.Add(
33
+ CgroupLookupKnown,
34
+ OrchestratorK8s,
35
+ []byte("/sys/fs/cgroup/a"),
36
+ []byte("pod-a"),
37
+ labels(struct{ Key, Value []byte }{[]byte("namespace"), []byte("default")}),
38
+ ); err != nil {
39
+ t.Fatalf("add known: %v", err)
40
+ }
41
+ if err := builder.Add(
42
+ CgroupLookupUnknownPermanent,
43
+ 0,
44
+ []byte("/system.slice/docker-abc.scope"),
45
+ nil,
46
+ nil,
47
+ ); err != nil {
48
+ t.Fatalf("add unknown: %v", err)
49
+ }
50
+ total := builder.Finish()
51
+ view, err := DecodeCgroupsLookupResponse(resp[:total])
52
+ if err != nil {
53
+ t.Fatalf("decode response: %v", err)
54
+ }
55
+ if view.ItemCount != 2 || view.Generation != 123 {
56
+ t.Fatalf("header = count %d generation %d", view.ItemCount, view.Generation)
57
+ }
58
+ got, err := view.Item(0)
59
+ if err != nil {
60
+ t.Fatalf("response item 0: %v", err)
61
+ }
62
+ if got.Status != CgroupLookupKnown || got.Orchestrator != OrchestratorK8s ||
63
+ got.Path.String() != "/sys/fs/cgroup/a" || got.Name.String() != "pod-a" ||
64
+ got.LabelCount != 1 {
65
+ t.Fatalf("bad known item: %+v", got)
66
+ }
67
+ label, err := got.Label(0)
68
+ if err != nil {
69
+ t.Fatalf("label 0: %v", err)
70
+ }
71
+ if label.Key.String() != "namespace" || label.Value.String() != "default" {
72
+ t.Fatalf("bad label: %q=%q", label.Key.String(), label.Value.String())
73
+ }
74
+ got, err = view.Item(1)
75
+ if err != nil {
76
+ t.Fatalf("response item 1: %v", err)
77
+ }
78
+ if got.Status != CgroupLookupUnknownPermanent || got.Name.Len() != 0 {
79
+ t.Fatalf("bad unknown item: %+v", got)
80
+ }
81
+}
82
+
83
+func TestAppsLookupRoundTrip(t *testing.T) {
84
+ var req [128]byte
85
+ n, err := EncodeAppsLookupRequest([]uint32{0, 1234, 9999}, req[:])
86
+ if err != nil {
87
+ t.Fatalf("encode request: %v", err)
88
+ }
89
+ reqView, err := DecodeAppsLookupRequest(req[:n])
90
+ if err != nil {
91
+ t.Fatalf("decode request: %v", err)
92
+ }
93
+ pid, err := reqView.Item(0)
94
+ if err != nil || pid != 0 {
95
+ t.Fatalf("item 0 pid = %d, err=%v", pid, err)
96
+ }
97
+
98
+ var resp [2048]byte
99
+ builder := NewAppsLookupBuilder(resp[:], 3, 77)
100
+ if err := builder.Add(
101
+ PidLookupKnown,
102
+ AppsCgroupKnown,
103
+ OrchestratorDocker,
104
+ 1234,
105
+ 1,
106
+ 1000,
107
+ ^uint64(0),
108
+ []byte("nginx"),
109
+ []byte("/docker/abc"),
110
+ []byte("container-a"),
111
+ labels(struct{ Key, Value []byte }{[]byte("image"), []byte("nginx:latest")}),
112
+ ); err != nil {
113
+ t.Fatalf("add known: %v", err)
114
+ }
115
+ if err := builder.Add(
116
+ PidLookupKnown,
117
+ AppsCgroupHostRoot,
118
+ 0,
119
+ 0,
120
+ 0,
121
+ 0,
122
+ 0,
123
+ []byte("swapper"),
124
+ nil,
125
+ nil,
126
+ nil,
127
+ ); err != nil {
128
+ t.Fatalf("add host root: %v", err)
129
+ }
130
+ if err := builder.Add(
131
+ PidLookupUnknown,
132
+ AppsCgroupKnown,
133
+ 0,
134
+ 9999,
135
+ 0,
136
+ NipcUIDUnset,
137
+ 0,
138
+ nil,
139
+ nil,
140
+ nil,
141
+ nil,
142
+ ); err != nil {
143
+ t.Fatalf("add unknown: %v", err)
144
+ }
145
+ total := builder.Finish()
146
+ view, err := DecodeAppsLookupResponse(resp[:total])
147
+ if err != nil {
148
+ t.Fatalf("decode response: %v", err)
149
+ }
150
+ if view.ItemCount != 3 || view.Generation != 77 {
151
+ t.Fatalf("header = count %d generation %d", view.ItemCount, view.Generation)
152
+ }
153
+ item0, err := view.Item(0)
154
+ if err != nil {
155
+ t.Fatalf("item 0: %v", err)
156
+ }
157
+ if item0.Pid != 1234 || item0.Status != PidLookupKnown ||
158
+ item0.CgroupStatus != AppsCgroupKnown ||
159
+ item0.Comm.String() != "nginx" ||
160
+ item0.CgroupPath.String() != "/docker/abc" ||
161
+ item0.Starttime != ^uint64(0) {
162
+ t.Fatalf("bad known item: %+v", item0)
163
+ }
164
+ item1, err := view.Item(1)
165
+ if err != nil {
166
+ t.Fatalf("item 1: %v", err)
167
+ }
168
+ if item1.Pid != 0 || item1.CgroupStatus != AppsCgroupHostRoot || item1.CgroupPath.Len() != 0 {
169
+ t.Fatalf("bad host-root item: %+v", item1)
170
+ }
171
+ item2, err := view.Item(2)
172
+ if err != nil {
173
+ t.Fatalf("item 2: %v", err)
174
+ }
175
+ if item2.Pid != 9999 || item2.Status != PidLookupUnknown || item2.Uid != NipcUIDUnset {
176
+ t.Fatalf("bad unknown item: %+v", item2)
177
+ }
178
+}
179
+
180
+func TestLookupValidationEdges(t *testing.T) {
181
+ var req [128]byte
182
+ if _, err := EncodeCgroupsLookupRequest([][]byte{[]byte("bad\x00path")}, req[:]); err != ErrBadLayout {
183
+ t.Fatalf("interior NUL request error = %v, want ErrBadLayout", err)
184
+ }
185
+
186
+ var resp [256]byte
187
+ builder := NewAppsLookupBuilder(resp[:], 1, 0)
188
+ if err := builder.Add(
189
+ PidLookupKnown,
190
+ AppsCgroupHostRoot,
191
+ 0,
192
+ 1,
193
+ 0,
194
+ 0,
195
+ 1,
196
+ []byte("1234567890123456"),
197
+ nil,
198
+ nil,
199
+ nil,
200
+ ); err != ErrBadLayout {
201
+ t.Fatalf("comm len 16 error = %v, want ErrBadLayout", err)
202
+ }
203
+
204
+ cg := NewCgroupsLookupBuilder(resp[:], 1, 0)
205
+ if err := cg.Add(CgroupLookupKnown, 99, []byte("/x"), nil, nil); err != nil {
206
+ t.Fatalf("unknown orchestrator should be accepted: %v", err)
207
+ }
208
+ total := cg.Finish()
209
+ itemStart := CgroupsLookupRespHdr + LookupDirEntrySize + int(ne.Uint32(resp[CgroupsLookupRespHdr:CgroupsLookupRespHdr+4]))
210
+ ne.PutUint16(resp[itemStart+2:itemStart+4], 99)
211
+ if _, err := DecodeCgroupsLookupResponse(resp[:total]); err != ErrBadLayout {
212
+ t.Fatalf("bad status error = %v, want ErrBadLayout", err)
213
+ }
214
+}
215
+
216
+func TestLookupDispatchRejectsShortResponseBuffer(t *testing.T) {
217
+ var req [128]byte
218
+ reqLen, err := EncodeCgroupsLookupRequest([][]byte{[]byte("/x")}, req[:])
219
+ if err != nil {
220
+ t.Fatalf("encode cgroups request: %v", err)
221
+ }
222
+ shortCgroups := make([]byte, CgroupsLookupRespHdr+LookupDirEntrySize-1)
223
+ n, err := DispatchCgroupsLookup(req[:reqLen], shortCgroups, func(*CgroupsLookupRequestView, *CgroupsLookupBuilder) bool {
224
+ t.Fatal("handler should not run with undersized response buffer")
225
+ return false
226
+ })
227
+ if err != ErrOverflow || n != 0 {
228
+ t.Fatalf("cgroups dispatch = n %d err %v, want 0 ErrOverflow", n, err)
229
+ }
230
+
231
+ reqLen, err = EncodeAppsLookupRequest([]uint32{1234}, req[:])
232
+ if err != nil {
233
+ t.Fatalf("encode apps request: %v", err)
234
+ }
235
+ shortApps := make([]byte, AppsLookupRespHdr+LookupDirEntrySize-1)
236
+ n, err = DispatchAppsLookup(req[:reqLen], shortApps, func(*AppsLookupRequestView, *AppsLookupBuilder) bool {
237
+ t.Fatal("handler should not run with undersized response buffer")
238
+ return false
239
+ })
240
+ if err != ErrOverflow || n != 0 {
241
+ t.Fatalf("apps dispatch = n %d err %v, want 0 ErrOverflow", n, err)
242
+ }
243
+}
244
+
245
+func TestLookupDecodeRejectsMaxUint32DirectoryOffset(t *testing.T) {
246
+ var resp [512]byte
247
+ cg := NewCgroupsLookupBuilder(resp[:], 1, 0)
248
+ if err := cg.Add(CgroupLookupKnown, OrchestratorDocker, []byte("/x"), nil, nil); err != nil {
249
+ t.Fatalf("add cgroups item: %v", err)
250
+ }
251
+ total := cg.Finish()
252
+ ne.PutUint32(resp[CgroupsLookupRespHdr:CgroupsLookupRespHdr+4], ^uint32(0)-7)
253
+ if _, err := DecodeCgroupsLookupResponse(resp[:total]); err != ErrOutOfBounds {
254
+ t.Fatalf("cgroups response with max offset error = %v, want ErrOutOfBounds", err)
255
+ }
256
+
257
+ apps := NewAppsLookupBuilder(resp[:], 1, 0)
258
+ if err := apps.Add(
259
+ PidLookupKnown,
260
+ AppsCgroupHostRoot,
261
+ 0,
262
+ 1234,
263
+ 1,
264
+ 1000,
265
+ 42,
266
+ []byte("nginx"),
267
+ nil,
268
+ nil,
269
+ nil,
270
+ ); err != nil {
271
+ t.Fatalf("add apps item: %v", err)
272
+ }
273
+ total = apps.Finish()
274
+ ne.PutUint32(resp[AppsLookupRespHdr:AppsLookupRespHdr+4], ^uint32(0)-7)
275
+ if _, err := DecodeAppsLookupResponse(resp[:total]); err != ErrOutOfBounds {
276
+ t.Fatalf("apps response with max offset error = %v, want ErrOutOfBounds", err)
277
+ }
278
+}
279
+
280
+func TestLookupLabelLayoutOverflow(t *testing.T) {
281
+ sample := labels(struct{ Key, Value []byte }{[]byte("k"), []byte("v")})
282
+ maxInt := int(^uint(0) >> 1)
283
+
284
+ if _, _, _, err := labelLayoutGo(maxInt-3, sample); err != ErrOverflow {
285
+ t.Fatalf("align overflow error = %v, want ErrOverflow", err)
286
+ }
287
+ if _, _, _, err := labelLayoutGo(maxInt-15, sample); err != ErrOverflow {
288
+ t.Fatalf("label table overflow error = %v, want ErrOverflow", err)
289
+ }
290
+}
291
+
292
+func expectPanic(t *testing.T, name string, fn func()) {
293
+ t.Helper()
294
+ defer func() {
295
+ if recover() == nil {
296
+ t.Fatalf("%s did not panic", name)
297
+ }
298
+ }()
299
+ fn()
300
+}
301
+
302
+func cgroupsLookupItemStart(t *testing.T, buf []byte) int {
303
+ t.Helper()
304
+ return CgroupsLookupRespHdr + LookupDirEntrySize +
305
+ int(ne.Uint32(buf[CgroupsLookupRespHdr:CgroupsLookupRespHdr+4]))
306
+}
307
+
308
+func appsLookupItemStart(t *testing.T, buf []byte) int {
309
+ t.Helper()
310
+ return AppsLookupRespHdr + LookupDirEntrySize +
311
+ int(ne.Uint32(buf[AppsLookupRespHdr:AppsLookupRespHdr+4]))
312
+}
313
+
314
+func validCgroupsLookupItemBytes(t *testing.T) []byte {
315
+ t.Helper()
316
+ var resp [512]byte
317
+ builder := NewCgroupsLookupBuilder(resp[:], 1, 0)
318
+ if err := builder.Add(
319
+ CgroupLookupKnown,
320
+ OrchestratorDocker,
321
+ []byte("/x"),
322
+ []byte("name"),
323
+ labels(struct{ Key, Value []byte }{[]byte("k"), []byte("v")}),
324
+ ); err != nil {
325
+ t.Fatalf("add cgroups item: %v", err)
326
+ }
327
+ total := builder.Finish()
328
+ start := cgroupsLookupItemStart(t, resp[:])
329
+ length := int(ne.Uint32(resp[CgroupsLookupRespHdr+4 : CgroupsLookupRespHdr+8]))
330
+ return append([]byte(nil), resp[start:start+length]...)[:min(length, total-start)]
331
+}
332
+
333
+func validAppsLookupItemBytes(t *testing.T) []byte {
334
+ t.Helper()
335
+ var resp [1024]byte
336
+ builder := NewAppsLookupBuilder(resp[:], 1, 0)
337
+ if err := builder.Add(
338
+ PidLookupKnown,
339
+ AppsCgroupKnown,
340
+ OrchestratorDocker,
341
+ 1234, 1, 1000, 42,
342
+ []byte("nginx"),
343
+ []byte("/docker/abc"),
344
+ []byte("container-a"),
345
+ labels(struct{ Key, Value []byte }{[]byte("k"), []byte("v")}),
346
+ ); err != nil {
347
+ t.Fatalf("add apps item: %v", err)
348
+ }
349
+ total := builder.Finish()
350
+ start := appsLookupItemStart(t, resp[:])
351
+ length := int(ne.Uint32(resp[AppsLookupRespHdr+4 : AppsLookupRespHdr+8]))
352
+ return append([]byte(nil), resp[start:start+length]...)[:min(length, total-start)]
353
+}
354
+
355
+func TestLookupRequestValidationCoverage(t *testing.T) {
356
+ var req [128]byte
357
+
358
+ n, err := EncodeCgroupsLookupRequest(nil, req[:])
359
+ if err != nil {
360
+ t.Fatalf("encode empty cgroups request: %v", err)
361
+ }
362
+ cgView, err := DecodeCgroupsLookupRequest(req[:n])
363
+ if err != nil || cgView.ItemCount != 0 {
364
+ t.Fatalf("decode empty cgroups request = count %d err %v", cgView.ItemCount, err)
365
+ }
366
+ if _, err := cgView.Item(0); err != ErrOutOfBounds {
367
+ t.Fatalf("empty cgroups item error = %v, want ErrOutOfBounds", err)
368
+ }
369
+ if _, err := EncodeCgroupsLookupRequest([][]byte{[]byte("/x")}, req[:CgroupsLookupReqHdr]); err != ErrOverflow {
370
+ t.Fatalf("short cgroups request buffer error = %v, want ErrOverflow", err)
371
+ }
372
+ if _, err := EncodeCgroupsLookupRequest([][]byte{[]byte("/xy")}, make([]byte, CgroupsLookupReqHdr+LookupDirEntrySize+1)); err != ErrOverflow {
373
+ t.Fatalf("packed cgroups request overflow error = %v, want ErrOverflow", err)
374
+ }
375
+
376
+ n, err = EncodeCgroupsLookupRequest([][]byte{[]byte("/xy")}, req[:])
377
+ if err != nil {
378
+ t.Fatalf("encode cgroups request: %v", err)
379
+ }
380
+ if _, err := DecodeCgroupsLookupRequest(req[:CgroupsLookupReqHdr]); err != ErrTruncated {
381
+ t.Fatalf("cgroups request truncated dir error = %v, want ErrTruncated", err)
382
+ }
383
+ cgManualReq := make([]byte, CgroupsLookupReqHdr+LookupDirEntrySize)
384
+ ne.PutUint32(cgManualReq[CgroupsLookupReqHdr+4:CgroupsLookupReqHdr+8], 8)
385
+ if _, err := (&CgroupsLookupRequestView{ItemCount: 1, payload: cgManualReq}).Item(0); err != ErrOutOfBounds {
386
+ t.Fatalf("manual cgroups request item error = %v, want ErrOutOfBounds", err)
387
+ }
388
+ for _, tc := range []struct {
389
+ name string
390
+ edit func([]byte)
391
+ want error
392
+ }{
393
+ {
394
+ name: "bad layout",
395
+ edit: func(b []byte) {
396
+ ne.PutUint16(b[0:2], 2)
397
+ },
398
+ want: ErrBadLayout,
399
+ },
400
+ {
401
+ name: "bad flags",
402
+ edit: func(b []byte) {
403
+ ne.PutUint16(b[2:4], 1)
404
+ },
405
+ want: ErrBadLayout,
406
+ },
407
+ {
408
+ name: "bad reserved",
409
+ edit: func(b []byte) {
410
+ ne.PutUint32(b[8:12], 1)
411
+ },
412
+ want: ErrBadLayout,
413
+ },
414
+ {
415
+ name: "bad alignment",
416
+ edit: func(b []byte) {
417
+ ne.PutUint32(b[CgroupsLookupReqHdr:CgroupsLookupReqHdr+4], 1)
418
+ },
419
+ want: ErrBadAlignment,
420
+ },
421
+ {
422
+ name: "too short item",
423
+ edit: func(b []byte) {
424
+ ne.PutUint32(b[CgroupsLookupReqHdr+4:CgroupsLookupReqHdr+8], 1)
425
+ },
426
+ want: ErrBadLayout,
427
+ },
428
+ {
429
+ name: "missing nul",
430
+ edit: func(b []byte) {
431
+ dirEnd := CgroupsLookupReqHdr + LookupDirEntrySize
432
+ b[dirEnd+3] = '!'
433
+ },
434
+ want: ErrMissingNul,
435
+ },
436
+ {
437
+ name: "interior nul",
438
+ edit: func(b []byte) {
439
+ dirEnd := CgroupsLookupReqHdr + LookupDirEntrySize
440
+ b[dirEnd+1] = 0
441
+ },
442
+ want: ErrBadLayout,
443
+ },
444
+ } {
445
+ bad := append([]byte(nil), req[:n]...)
446
+ tc.edit(bad)
447
+ if _, err := DecodeCgroupsLookupRequest(bad); err != tc.want {
448
+ t.Fatalf("decode cgroups request %s error = %v, want %v", tc.name, err, tc.want)
449
+ }
450
+ }
451
+
452
+ n, err = EncodeAppsLookupRequest(nil, req[:])
453
+ if err != nil {
454
+ t.Fatalf("encode empty apps request: %v", err)
455
+ }
456
+ appsView, err := DecodeAppsLookupRequest(req[:n])
457
+ if err != nil || appsView.ItemCount != 0 {
458
+ t.Fatalf("decode empty apps request = count %d err %v", appsView.ItemCount, err)
459
+ }
460
+ if _, err := appsView.Item(0); err != ErrOutOfBounds {
461
+ t.Fatalf("empty apps item error = %v, want ErrOutOfBounds", err)
462
+ }
463
+ if _, err := EncodeAppsLookupRequest([]uint32{1234}, req[:AppsLookupReqHdr]); err != ErrOverflow {
464
+ t.Fatalf("short apps request buffer error = %v, want ErrOverflow", err)
465
+ }
466
+ if _, err := EncodeAppsLookupRequest([]uint32{1234}, make([]byte, AppsLookupReqHdr+LookupDirEntrySize+AppsLookupKeySize-1)); err != ErrOverflow {
467
+ t.Fatalf("packed apps request overflow error = %v, want ErrOverflow", err)
468
+ }
469
+
470
+ n, err = EncodeAppsLookupRequest([]uint32{1234}, req[:])
471
+ if err != nil {
472
+ t.Fatalf("encode apps request: %v", err)
473
+ }
474
+ if _, err := DecodeAppsLookupRequest(req[:AppsLookupReqHdr]); err != ErrTruncated {
475
+ t.Fatalf("apps request truncated dir error = %v, want ErrTruncated", err)
476
+ }
477
+ appsManualReq := make([]byte, AppsLookupReqHdr+LookupDirEntrySize)
478
+ ne.PutUint32(appsManualReq[AppsLookupReqHdr+4:AppsLookupReqHdr+8], AppsLookupKeySize)
479
+ if _, err := (&AppsLookupRequestView{ItemCount: 1, payload: appsManualReq}).Item(0); err != ErrOutOfBounds {
480
+ t.Fatalf("manual apps request item error = %v, want ErrOutOfBounds", err)
481
+ }
482
+ for _, tc := range []struct {
483
+ name string
484
+ edit func([]byte)
485
+ want error
486
+ }{
487
+ {
488
+ name: "bad layout",
489
+ edit: func(b []byte) {
490
+ ne.PutUint16(b[0:2], 2)
491
+ },
492
+ want: ErrBadLayout,
493
+ },
494
+ {
495
+ name: "bad item length",
496
+ edit: func(b []byte) {
497
+ ne.PutUint32(b[AppsLookupReqHdr+4:AppsLookupReqHdr+8], AppsLookupKeySize-1)
498
+ },
499
+ want: ErrBadLayout,
500
+ },
501
+ {
502
+ name: "bad key reserved",
503
+ edit: func(b []byte) {
504
+ dirEnd := AppsLookupReqHdr + LookupDirEntrySize
505
+ ne.PutUint32(b[dirEnd+4:dirEnd+8], 1)
506
+ },
507
+ want: ErrBadLayout,
508
+ },
509
+ } {
510
+ bad := append([]byte(nil), req[:n]...)
511
+ tc.edit(bad)
512
+ if _, err := DecodeAppsLookupRequest(bad); err != tc.want {
513
+ t.Fatalf("decode apps request %s error = %v, want %v", tc.name, err, tc.want)
514
+ }
515
+ }
516
+}
517
+
518
+func TestCgroupsLookupBuilderGuardCoverage(t *testing.T) {
519
+ expectPanic(t, "small cgroups builder", func() {
520
+ _ = NewCgroupsLookupBuilder(make([]byte, CgroupsLookupRespHdr+LookupDirEntrySize-1), 1, 0)
521
+ })
522
+
523
+ var empty [CgroupsLookupRespHdr]byte
524
+ emptyBuilder := NewCgroupsLookupBuilder(empty[:], 0, 1)
525
+ emptyBuilder.SetGeneration(2)
526
+ if total := emptyBuilder.Finish(); total != CgroupsLookupRespHdr {
527
+ t.Fatalf("empty cgroups finish = %d", total)
528
+ }
529
+ emptyView, err := DecodeCgroupsLookupResponse(empty[:])
530
+ if err != nil || emptyView.Generation != 2 || emptyView.ItemCount != 0 {
531
+ t.Fatalf("empty cgroups decode = generation %d count %d err %v", emptyView.Generation, emptyView.ItemCount, err)
532
+ }
533
+
534
+ var resp [512]byte
535
+ builder := NewCgroupsLookupBuilder(resp[:], 1, 10)
536
+ builder.SetGeneration(11)
537
+ if builder.ItemCount() != 0 || builder.Error() != nil {
538
+ t.Fatalf("fresh cgroups builder count/error = %d/%v", builder.ItemCount(), builder.Error())
539
+ }
540
+ err = builder.Add(
541
+ CgroupLookupKnown,
542
+ OrchestratorK8s,
543
+ []byte("/x"),
544
+ []byte("pod"),
545
+ labels(struct{ Key, Value []byte }{[]byte("namespace"), []byte("default")}),
546
+ )
547
+ if err != nil {
548
+ t.Fatalf("add cgroups known: %v", err)
549
+ }
550
+ if builder.ItemCount() != 1 || builder.Error() != nil {
551
+ t.Fatalf("used cgroups builder count/error = %d/%v", builder.ItemCount(), builder.Error())
552
+ }
553
+ if err := builder.Add(CgroupLookupKnown, 0, []byte("/overflow"), nil, nil); err != ErrOverflow {
554
+ t.Fatalf("cgroups overflow error = %v, want ErrOverflow", err)
555
+ }
556
+ if builder.Error() != ErrOverflow {
557
+ t.Fatalf("cgroups builder error = %v, want ErrOverflow", builder.Error())
558
+ }
559
+ total := builder.Finish()
560
+ view, err := DecodeCgroupsLookupResponse(resp[:total])
561
+ if err != nil || view.Generation != 11 {
562
+ t.Fatalf("decode cgroups builder response = generation %d err %v", view.Generation, err)
563
+ }
564
+ item, err := view.Item(0)
565
+ if err != nil {
566
+ t.Fatalf("cgroups response item: %v", err)
567
+ }
568
+ if _, err := item.Label(1); err != ErrOutOfBounds {
569
+ t.Fatalf("cgroups label out-of-bounds error = %v, want ErrOutOfBounds", err)
570
+ }
571
+ if _, err := view.Item(1); err != ErrOutOfBounds {
572
+ t.Fatalf("cgroups item out-of-bounds error = %v, want ErrOutOfBounds", err)
573
+ }
574
+
575
+ for _, tc := range []struct {
576
+ name string
577
+ add func(*CgroupsLookupBuilder) error
578
+ want error
579
+ }{
580
+ {
581
+ name: "bad status",
582
+ add: func(b *CgroupsLookupBuilder) error {
583
+ return b.Add(99, 0, []byte("/x"), nil, nil)
584
+ },
585
+ want: ErrBadLayout,
586
+ },
587
+ {
588
+ name: "empty path",
589
+ add: func(b *CgroupsLookupBuilder) error {
590
+ return b.Add(CgroupLookupKnown, 0, nil, nil, nil)
591
+ },
592
+ want: ErrBadLayout,
593
+ },
594
+ {
595
+ name: "bad name",
596
+ add: func(b *CgroupsLookupBuilder) error {
597
+ return b.Add(CgroupLookupKnown, 0, []byte("/x"), []byte("bad\x00name"), nil)
598
+ },
599
+ want: ErrBadLayout,
600
+ },
601
+ {
602
+ name: "unknown with name",
603
+ add: func(b *CgroupsLookupBuilder) error {
604
+ return b.Add(CgroupLookupUnknownRetryLater, 0, []byte("/x"), []byte("name"), nil)
605
+ },
606
+ want: ErrBadLayout,
607
+ },
608
+ {
609
+ name: "bad label",
610
+ add: func(b *CgroupsLookupBuilder) error {
611
+ return b.Add(CgroupLookupKnown, 0, []byte("/x"), nil,
612
+ labels(struct{ Key, Value []byte }{[]byte{}, []byte("v")}))
613
+ },
614
+ want: ErrBadLayout,
615
+ },
616
+ } {
617
+ b := NewCgroupsLookupBuilder(resp[:], 1, 0)
618
+ if err := tc.add(b); err != tc.want {
619
+ t.Fatalf("cgroups builder %s error = %v, want %v", tc.name, err, tc.want)
620
+ }
621
+ }
622
+ tooManyLabels := make([]struct{ Key, Value []byte }, int(^uint16(0))+1)
623
+ if err := NewCgroupsLookupBuilder(resp[:], 1, 0).Add(CgroupLookupKnown, 0, []byte("/x"), nil, tooManyLabels); err != ErrOverflow {
624
+ t.Fatalf("cgroups too-many-labels error = %v, want ErrOverflow", err)
625
+ }
626
+
627
+ small := make([]byte, CgroupsLookupRespHdr+LookupDirEntrySize)
628
+ smallBuilder := NewCgroupsLookupBuilder(small, 1, 0)
629
+ if err := smallBuilder.Add(CgroupLookupKnown, 0, []byte("/x"), nil, nil); err != ErrOverflow {
630
+ t.Fatalf("small cgroups builder add error = %v, want ErrOverflow", err)
631
+ }
632
+ negativeOffsetBuilder := &CgroupsLookupBuilder{buf: make([]byte, 512), maxItems: 1, dataOffset: -1}
633
+ if err := negativeOffsetBuilder.Add(CgroupLookupKnown, 0, []byte("/x"), nil, nil); err != ErrOverflow {
634
+ t.Fatalf("negative-offset cgroups builder add error = %v, want ErrOverflow", err)
635
+ }
636
+ overflowOffsetBuilder := &CgroupsLookupBuilder{buf: make([]byte, 512), maxItems: 1, dataOffset: maxIntValue() - 32}
637
+ if err := overflowOffsetBuilder.Add(CgroupLookupKnown, 0, []byte("/x"), nil, nil); err != ErrOverflow {
638
+ t.Fatalf("overflow-offset cgroups builder add error = %v, want ErrOverflow", err)
639
+ }
640
+}
641
+
642
+func TestAppsLookupBuilderGuardCoverage(t *testing.T) {
643
+ expectPanic(t, "small apps builder", func() {
644
+ _ = NewAppsLookupBuilder(make([]byte, AppsLookupRespHdr+LookupDirEntrySize-1), 1, 0)
645
+ })
646
+
647
+ var empty [AppsLookupRespHdr]byte
648
+ emptyBuilder := NewAppsLookupBuilder(empty[:], 0, 1)
649
+ emptyBuilder.SetGeneration(2)
650
+ if total := emptyBuilder.Finish(); total != AppsLookupRespHdr {
651
+ t.Fatalf("empty apps finish = %d", total)
652
+ }
653
+ emptyView, err := DecodeAppsLookupResponse(empty[:])
654
+ if err != nil || emptyView.Generation != 2 || emptyView.ItemCount != 0 {
655
+ t.Fatalf("empty apps decode = generation %d count %d err %v", emptyView.Generation, emptyView.ItemCount, err)
656
+ }
657
+
658
+ var resp [1024]byte
659
+ builder := NewAppsLookupBuilder(resp[:], 4, 20)
660
+ builder.SetGeneration(21)
661
+ if builder.ItemCount() != 0 || builder.Error() != nil {
662
+ t.Fatalf("fresh apps builder count/error = %d/%v", builder.ItemCount(), builder.Error())
663
+ }
664
+ if err := builder.Add(
665
+ PidLookupKnown,
666
+ AppsCgroupKnown,
667
+ OrchestratorDocker,
668
+ 1234, 1, 1000, 42,
669
+ []byte("nginx"),
670
+ []byte("/docker/abc"),
671
+ []byte("container-a"),
672
+ labels(struct{ Key, Value []byte }{[]byte("image"), []byte("nginx")}),
673
+ ); err != nil {
674
+ t.Fatalf("add apps known: %v", err)
675
+ }
676
+ if err := builder.Add(
677
+ PidLookupKnown,
678
+ AppsCgroupUnknownRetryLater,
679
+ 0,
680
+ 1235, 1, 1000, 43,
681
+ []byte("worker"),
682
+ nil,
683
+ nil,
684
+ nil,
685
+ ); err != nil {
686
+ t.Fatalf("add apps retry: %v", err)
687
+ }
688
+ if err := builder.Add(
689
+ PidLookupKnown,
690
+ AppsCgroupUnknownPermanent,
691
+ 0,
692
+ 1236, 1, 1000, 44,
693
+ []byte("worker2"),
694
+ []byte("/gone"),
695
+ nil,
696
+ nil,
697
+ ); err != nil {
698
+ t.Fatalf("add apps permanent: %v", err)
699
+ }
700
+ if err := builder.Add(
701
+ PidLookupUnknown,
702
+ 0,
703
+ 0,
704
+ 9999, 0, NipcUIDUnset, 0,
705
+ nil, nil, nil, nil,
706
+ ); err != nil {
707
+ t.Fatalf("add apps unknown: %v", err)
708
+ }
709
+ if builder.ItemCount() != 4 || builder.Error() != nil {
710
+ t.Fatalf("used apps builder count/error = %d/%v", builder.ItemCount(), builder.Error())
711
+ }
712
+ total := builder.Finish()
713
+ view, err := DecodeAppsLookupResponse(resp[:total])
714
+ if err != nil || view.Generation != 21 {
715
+ t.Fatalf("decode apps builder response = generation %d err %v", view.Generation, err)
716
+ }
717
+ item, err := view.Item(0)
718
+ if err != nil {
719
+ t.Fatalf("apps response item: %v", err)
720
+ }
721
+ if _, err := item.Label(1); err != ErrOutOfBounds {
722
+ t.Fatalf("apps label out-of-bounds error = %v, want ErrOutOfBounds", err)
723
+ }
724
+ if _, err := view.Item(4); err != ErrOutOfBounds {
725
+ t.Fatalf("apps item out-of-bounds error = %v, want ErrOutOfBounds", err)
726
+ }
727
+ item, err = view.Item(1)
728
+ if err != nil || item.CgroupStatus != AppsCgroupUnknownRetryLater {
729
+ t.Fatalf("apps retry item = %+v err %v", item, err)
730
+ }
731
+ if len(item.CgroupPath.Bytes()) != 0 {
732
+ t.Fatalf("apps retry cgroup path = %q, want empty", item.CgroupPath.Bytes())
733
+ }
734
+ item, err = view.Item(2)
735
+ if err != nil || item.CgroupStatus != AppsCgroupUnknownPermanent {
736
+ t.Fatalf("apps permanent item = %+v err %v", item, err)
737
+ }
738
+ item, err = view.Item(3)
739
+ if err != nil || item.Status != PidLookupUnknown {
740
+ t.Fatalf("apps unknown item = %+v err %v", item, err)
741
+ }
742
+ if err := builder.Add(PidLookupUnknown, 0, 0, 10000, 0, NipcUIDUnset, 0, nil, nil, nil, nil); err != ErrOverflow {
743
+ t.Fatalf("apps overflow error = %v, want ErrOverflow", err)
744
+ }
745
+ if builder.Error() != ErrOverflow {
746
+ t.Fatalf("apps builder error = %v, want ErrOverflow", builder.Error())
747
+ }
748
+
749
+ for _, tc := range []struct {
750
+ name string
751
+ add func(*AppsLookupBuilder) error
752
+ want error
753
+ }{
754
+ {
755
+ name: "bad status",
756
+ add: func(b *AppsLookupBuilder) error {
757
+ return b.Add(99, 0, 0, 1, 0, 0, 0, []byte("x"), nil, nil, nil)
758
+ },
759
+ want: ErrBadLayout,
760
+ },
761
+ {
762
+ name: "bad cgroup status",
763
+ add: func(b *AppsLookupBuilder) error {
764
+ return b.Add(PidLookupKnown, 99, 0, 1, 0, 0, 0, []byte("x"), nil, nil, nil)
765
+ },
766
+ want: ErrBadLayout,
767
+ },
768
+ {
769
+ name: "bad comm",
770
+ add: func(b *AppsLookupBuilder) error {
771
+ return b.Add(PidLookupKnown, AppsCgroupHostRoot, 0, 1, 0, 0, 0, []byte("bad\x00comm"), nil, nil, nil)
772
+ },
773
+ want: ErrBadLayout,
774
+ },
775
+ {
776
+ name: "unknown with data",
777
+ add: func(b *AppsLookupBuilder) error {
778
+ return b.Add(PidLookupUnknown, 0, 0, 1, 2, NipcUIDUnset, 0, nil, nil, nil, nil)
779
+ },
780
+ want: ErrBadLayout,
781
+ },
782
+ {
783
+ name: "known missing path",
784
+ add: func(b *AppsLookupBuilder) error {
785
+ return b.Add(PidLookupKnown, AppsCgroupKnown, 0, 1, 0, 0, 0, []byte("x"), nil, nil, nil)
786
+ },
787
+ want: ErrBadLayout,
788
+ },
789
+ {
790
+ name: "retry with labels",
791
+ add: func(b *AppsLookupBuilder) error {
792
+ return b.Add(PidLookupKnown, AppsCgroupUnknownRetryLater, 0, 1, 0, 0, 0,
793
+ []byte("x"), []byte("/x"), nil,
794
+ labels(struct{ Key, Value []byte }{[]byte("k"), []byte("v")}))
795
+ },
796
+ want: ErrBadLayout,
797
+ },
798
+ {
799
+ name: "permanent missing path",
800
+ add: func(b *AppsLookupBuilder) error {
801
+ return b.Add(PidLookupKnown, AppsCgroupUnknownPermanent, 0, 1, 0, 0, 0,
802
+ []byte("x"), nil, nil, nil)
803
+ },
804
+ want: ErrBadLayout,
805
+ },
806
+ {
807
+ name: "host root with path",
808
+ add: func(b *AppsLookupBuilder) error {
809
+ return b.Add(PidLookupKnown, AppsCgroupHostRoot, 0, 1, 0, 0, 0, []byte("x"), []byte("/x"), nil, nil)
810
+ },
811
+ want: ErrBadLayout,
812
+ },
813
+ {
814
+ name: "bad label",
815
+ add: func(b *AppsLookupBuilder) error {
816
+ return b.Add(PidLookupKnown, AppsCgroupKnown, 0, 1, 0, 0, 0,
817
+ []byte("x"), []byte("/x"), nil,
818
+ labels(struct{ Key, Value []byte }{[]byte("bad\x00key"), []byte("v")}))
819
+ },
820
+ want: ErrBadLayout,
821
+ },
822
+ } {
823
+ b := NewAppsLookupBuilder(resp[:], 1, 0)
824
+ if err := tc.add(b); err != tc.want {
825
+ t.Fatalf("apps builder %s error = %v, want %v", tc.name, err, tc.want)
826
+ }
827
+ }
828
+ tooManyLabels := make([]struct{ Key, Value []byte }, int(^uint16(0))+1)
829
+ if err := NewAppsLookupBuilder(resp[:], 1, 0).Add(PidLookupKnown, AppsCgroupKnown, 0, 1, 0, 0, 0, []byte("x"), []byte("/x"), nil, tooManyLabels); err != ErrOverflow {
830
+ t.Fatalf("apps too-many-labels error = %v, want ErrOverflow", err)
831
+ }
832
+
833
+ small := make([]byte, AppsLookupRespHdr+LookupDirEntrySize)
834
+ smallBuilder := NewAppsLookupBuilder(small, 1, 0)
835
+ if err := smallBuilder.Add(PidLookupKnown, AppsCgroupHostRoot, 0, 1, 0, 0, 0, []byte("x"), nil, nil, nil); err != ErrOverflow {
836
+ t.Fatalf("small apps builder add error = %v, want ErrOverflow", err)
837
+ }
838
+ negativeOffsetBuilder := &AppsLookupBuilder{buf: make([]byte, 512), maxItems: 1, dataOffset: -1}
839
+ if err := negativeOffsetBuilder.Add(PidLookupKnown, AppsCgroupHostRoot, 0, 1, 0, 0, 0, []byte("x"), nil, nil, nil); err != ErrOverflow {
840
+ t.Fatalf("negative-offset apps builder add error = %v, want ErrOverflow", err)
841
+ }
842
+ overflowOffsetBuilder := &AppsLookupBuilder{buf: make([]byte, 512), maxItems: 1, dataOffset: maxIntValue() - 32}
843
+ if err := overflowOffsetBuilder.Add(PidLookupKnown, AppsCgroupHostRoot, 0, 1, 0, 0, 0, []byte("x"), nil, nil, nil); err != ErrOverflow {
844
+ t.Fatalf("overflow-offset apps builder add error = %v, want ErrOverflow", err)
845
+ }
846
+}
847
+
848
+func TestAppsLookupUnknownItemCanonicalLayout(t *testing.T) {
849
+ var resp [128]byte
850
+ builder := NewAppsLookupBuilder(resp[:], 1, 7)
851
+ if err := builder.Add(PidLookupUnknown, AppsCgroupKnown, 0, 4321, 0, NipcUIDUnset, 0, nil, nil, nil, nil); err != nil {
852
+ t.Fatalf("add unknown apps item: %v", err)
853
+ }
854
+ total := builder.Finish()
855
+ itemStart := appsLookupItemStart(t, resp[:total])
856
+ item := resp[itemStart:total]
857
+
858
+ if got := len(item); got != appsLookupUnknownItemSize {
859
+ t.Fatalf("unknown item size = %d, want %d", got, appsLookupUnknownItemSize)
860
+ }
861
+ if got := ne.Uint32(resp[AppsLookupRespHdr+4 : AppsLookupRespHdr+8]); got != appsLookupUnknownItemSize {
862
+ t.Fatalf("directory item length = %d, want %d", got, appsLookupUnknownItemSize)
863
+ }
864
+ if got := ne.Uint32(item[32:36]); got != AppsLookupItemHdr {
865
+ t.Fatalf("comm offset = %d, want %d", got, AppsLookupItemHdr)
866
+ }
867
+ if got := ne.Uint32(item[40:44]); got != AppsLookupItemHdr+1 {
868
+ t.Fatalf("path offset = %d, want %d", got, AppsLookupItemHdr+1)
869
+ }
870
+ if got := ne.Uint32(item[48:52]); got != AppsLookupItemHdr+2 {
871
+ t.Fatalf("name offset = %d, want %d", got, AppsLookupItemHdr+2)
872
+ }
873
+ if item[AppsLookupItemHdr] != 0 || item[AppsLookupItemHdr+1] != 0 || item[AppsLookupItemHdr+2] != 0 {
874
+ t.Fatalf("unknown item NUL bytes = %v, want all zero", item[AppsLookupItemHdr:AppsLookupItemHdr+3])
875
+ }
876
+
877
+ view, err := DecodeAppsLookupResponse(resp[:total])
878
+ if err != nil {
879
+ t.Fatalf("decode unknown apps item: %v", err)
880
+ }
881
+ got, err := view.Item(0)
882
+ if err != nil {
883
+ t.Fatalf("read unknown apps item: %v", err)
884
+ }
885
+ if got.Status != PidLookupUnknown || got.Pid != 4321 ||
886
+ len(got.Comm.Bytes()) != 0 || len(got.CgroupPath.Bytes()) != 0 || len(got.CgroupName.Bytes()) != 0 {
887
+ t.Fatalf("decoded unknown apps item = %+v", got)
888
+ }
889
+
890
+ bad := append([]byte(nil), resp[:total]...)
891
+ bad[itemStart+AppsLookupItemHdr+1] = 'x'
892
+ if _, err := DecodeAppsLookupResponse(bad); err != ErrMissingNul {
893
+ t.Fatalf("decode missing unknown path NUL error = %v, want ErrMissingNul", err)
894
+ }
895
+}
896
+
897
+func TestCgroupsLookupUnknownItemCanonicalLayout(t *testing.T) {
898
+ path := []byte("/sys/fs/cgroup/bench/cg-001")
899
+ var resp [128]byte
900
+ builder := NewCgroupsLookupBuilder(resp[:], 1, 9)
901
+ if err := builder.Add(CgroupLookupUnknownRetryLater, 0, path, nil, nil); err != nil {
902
+ t.Fatalf("add unknown cgroups item: %v", err)
903
+ }
904
+ total := builder.Finish()
905
+ itemStart := cgroupsLookupItemStart(t, resp[:total])
906
+ item := resp[itemStart:total]
907
+ wantSize := cgroupsLookupUnknownFixedBytes + len(path) + 1
908
+
909
+ if got := len(item); got != wantSize {
910
+ t.Fatalf("unknown item size = %d, want %d", got, wantSize)
911
+ }
912
+ if got := ne.Uint32(resp[CgroupsLookupRespHdr+4 : CgroupsLookupRespHdr+8]); got != uint32(wantSize) {
913
+ t.Fatalf("directory item length = %d, want %d", got, wantSize)
914
+ }
915
+ if got := ne.Uint32(item[8:12]); got != CgroupsLookupItemHdr {
916
+ t.Fatalf("path offset = %d, want %d", got, CgroupsLookupItemHdr)
917
+ }
918
+ wantNameOff := CgroupsLookupItemHdr + len(path) + 1
919
+ if got := ne.Uint32(item[16:20]); got != uint32(wantNameOff) {
920
+ t.Fatalf("name offset = %d, want %d", got, wantNameOff)
921
+ }
922
+ if item[CgroupsLookupItemHdr+len(path)] != 0 || item[wantNameOff] != 0 {
923
+ t.Fatalf("unknown item NUL bytes are not canonical")
924
+ }
925
+
926
+ view, err := DecodeCgroupsLookupResponse(resp[:total])
927
+ if err != nil {
928
+ t.Fatalf("decode unknown cgroups item: %v", err)
929
+ }
930
+ got, err := view.Item(0)
931
+ if err != nil {
932
+ t.Fatalf("read unknown cgroups item: %v", err)
933
+ }
934
+ if got.Status != CgroupLookupUnknownRetryLater || got.Path.String() != string(path) ||
935
+ len(got.Name.Bytes()) != 0 || got.LabelCount != 0 {
936
+ t.Fatalf("decoded unknown cgroups item = %+v", got)
937
+ }
938
+
939
+ bad := append([]byte(nil), resp[:total]...)
940
+ bad[itemStart+wantNameOff] = 'x'
941
+ if _, err := DecodeCgroupsLookupResponse(bad); err != ErrMissingNul {
942
+ t.Fatalf("decode missing unknown name NUL error = %v, want ErrMissingNul", err)
943
+ }
944
+}
945
+
946
+func TestLookupInternalGuardCoverage(t *testing.T) {
947
+ if _, ok := checkedU32Int(-1); ok {
948
+ t.Fatalf("checkedU32Int(-1) succeeded")
949
+ }
950
+ if _, ok := checkedU32Int(int(uint64(^uint32(0)) + 1)); ok {
951
+ t.Fatalf("checkedU32Int(uint32 max + 1) succeeded")
952
+ }
953
+ if _, ok := checkedU16Int(-1); ok {
954
+ t.Fatalf("checkedU16Int(-1) succeeded")
955
+ }
956
+ if _, ok := checkedU16Int(int(^uint16(0)) + 1); ok {
957
+ t.Fatalf("checkedU16Int(uint16 max + 1) succeeded")
958
+ }
959
+ if _, ok := checkedInt(uint64(maxIntValue()) + 1); ok {
960
+ t.Fatalf("checkedInt(max + 1) succeeded")
961
+ }
962
+ if _, ok := checkedAddInt(-1, 0); ok {
963
+ t.Fatalf("checkedAddInt negative succeeded")
964
+ }
965
+ if _, ok := checkedAddInt(maxIntValue(), 1); ok {
966
+ t.Fatalf("checkedAddInt overflow succeeded")
967
+ }
968
+ if _, ok := checkedMulInt(-1, 1); ok {
969
+ t.Fatalf("checkedMulInt negative succeeded")
970
+ }
971
+ if _, ok := checkedMulInt(maxIntValue(), 2); ok {
972
+ t.Fatalf("checkedMulInt overflow succeeded")
973
+ }
974
+ if _, ok := checkedAlign8(-1); ok {
975
+ t.Fatalf("checkedAlign8 negative succeeded")
976
+ }
977
+ if _, ok := checkedAlign8(maxIntValue() - 6); ok {
978
+ t.Fatalf("checkedAlign8 overflow succeeded")
979
+ }
980
+ if _, err := lookupPayloadSlice([]byte{0}, -1, 0, 0); err != ErrOutOfBounds {
981
+ t.Fatalf("lookupPayloadSlice negative error = %v, want ErrOutOfBounds", err)
982
+ }
983
+ if _, err := lookupPayloadSlice([]byte{0}, 0, 0, 2); err != ErrOutOfBounds {
984
+ t.Fatalf("lookupPayloadSlice too long error = %v, want ErrOutOfBounds", err)
985
+ }
986
+ if err := validateLookupDir(make([]byte, 8), maxIntValue(), 1, 0, 0, -1); err != ErrBadItemCount {
987
+ t.Fatalf("validateLookupDir bad item count error = %v, want ErrBadItemCount", err)
988
+ }
989
+ if err := validateLookupDir(make([]byte, 0), 0, 1, 0, 0, -1); err != ErrTruncated {
990
+ t.Fatalf("validateLookupDir truncated error = %v, want ErrTruncated", err)
991
+ }
992
+ var dir [16]byte
993
+ ne.PutUint32(dir[0:4], 8)
994
+ ne.PutUint32(dir[4:8], 8)
995
+ ne.PutUint32(dir[8:12], 0)
996
+ ne.PutUint32(dir[12:16], 8)
997
+ if err := validateLookupDir(dir[:], 0, 2, 32, 1, -1); err != ErrBadLayout {
998
+ t.Fatalf("validateLookupDir overlap error = %v, want ErrBadLayout", err)
999
+ }
1000
+
1001
+ item := []byte{'a', 0, 'b', 0}
1002
+ if _, _, err := lookupString(item, 1, 0, 1); err != ErrOutOfBounds {
1003
+ t.Fatalf("lookupString below header error = %v, want ErrOutOfBounds", err)
1004
+ }
1005
+ if _, _, err := lookupString(item, 0, 3, 2); err != ErrOutOfBounds {
1006
+ t.Fatalf("lookupString oob error = %v, want ErrOutOfBounds", err)
1007
+ }
1008
+ if _, _, err := lookupString(item, 0, 0, 2); err != ErrMissingNul {
1009
+ t.Fatalf("lookupString missing nul error = %v, want ErrMissingNul", err)
1010
+ }
1011
+ if _, _, err := lookupString([]byte{'a', 0, 0}, 0, 0, 2); err != ErrBadLayout {
1012
+ t.Fatalf("lookupString interior nul error = %v, want ErrBadLayout", err)
1013
+ }
1014
+
1015
+ if _, err := lookupLabelAt(make([]byte, LookupLabelEntrySize), 0, 1, -1, 0); err != ErrOutOfBounds {
1016
+ t.Fatalf("lookupLabelAt bad table offset error = %v, want ErrOutOfBounds", err)
1017
+ }
1018
+
1019
+ labelItem := make([]byte, 52)
1020
+ ne.PutUint32(labelItem[32:36], 48)
1021
+ ne.PutUint32(labelItem[36:40], 1)
1022
+ ne.PutUint32(labelItem[40:44], 50)
1023
+ ne.PutUint32(labelItem[44:48], 1)
1024
+ labelItem[48] = 'k'
1025
+ labelItem[49] = 0
1026
+ labelItem[50] = 'v'
1027
+ labelItem[51] = 0
1028
+ if _, err := validateLabels(labelItem, 28, 0, 28); err != ErrBadLayout {
1029
+ t.Fatalf("validateLabels zero count wrong fixedEnd error = %v, want ErrBadLayout", err)
1030
+ }
1031
+ if _, err := validateLabels(labelItem, 28, 1, maxIntValue()-6); err != ErrOutOfBounds {
1032
+ t.Fatalf("validateLabels align overflow error = %v, want ErrOutOfBounds", err)
1033
+ }
1034
+ if _, err := validateLabels(labelItem, 28, 1, 60); err != ErrOutOfBounds {
1035
+ t.Fatalf("validateLabels table after end error = %v, want ErrOutOfBounds", err)
1036
+ }
1037
+ badPadding := append([]byte(nil), labelItem...)
1038
+ badPadding[28] = 1
1039
+ if _, err := validateLabels(badPadding, 28, 1, 28); err != ErrBadLayout {
1040
+ t.Fatalf("validateLabels bad padding error = %v, want ErrBadLayout", err)
1041
+ }
1042
+ if _, err := validateLabels(labelItem[:40], 28, 1, 28); err != ErrOutOfBounds {
1043
+ t.Fatalf("validateLabels table oob error = %v, want ErrOutOfBounds", err)
1044
+ }
1045
+ badKeyLen := append([]byte(nil), labelItem...)
1046
+ ne.PutUint32(badKeyLen[36:40], 0)
1047
+ if _, err := validateLabels(badKeyLen, 28, 1, 28); err != ErrBadLayout {
1048
+ t.Fatalf("validateLabels bad key length error = %v, want ErrBadLayout", err)
1049
+ }
1050
+ badKeyOff := append([]byte(nil), labelItem...)
1051
+ ne.PutUint32(badKeyOff[32:36], 49)
1052
+ if _, err := validateLabels(badKeyOff, 28, 1, 28); err != ErrBadLayout {
1053
+ t.Fatalf("validateLabels bad key offset error = %v, want ErrBadLayout", err)
1054
+ }
1055
+ badValueOff := append([]byte(nil), labelItem...)
1056
+ ne.PutUint32(badValueOff[40:44], 49)
1057
+ if _, err := validateLabels(badValueOff, 28, 1, 28); err != ErrBadLayout {
1058
+ t.Fatalf("validateLabels bad value offset error = %v, want ErrBadLayout", err)
1059
+ }
1060
+ badValueNul := append([]byte(nil), labelItem...)
1061
+ badValueNul[51] = '!'
1062
+ if _, err := validateLabels(badValueNul, 28, 1, 28); err != ErrMissingNul {
1063
+ t.Fatalf("validateLabels bad value nul error = %v, want ErrMissingNul", err)
1064
+ }
1065
+ extra := append([]byte(nil), labelItem...)
1066
+ extra = append(extra, 0)
1067
+ if _, err := validateLabels(extra, 28, 1, 28); err != ErrBadLayout {
1068
+ t.Fatalf("validateLabels extra byte error = %v, want ErrBadLayout", err)
1069
+ }
1070
+
1071
+ var compact [80]byte
1072
+ ne.PutUint32(compact[16:20], 40)
1073
+ copy(compact[40:44], []byte{1, 2, 3, 4})
1074
+ if n := finishLookupResponse(compact[:], 16, 1, 44, 7); n != 28 {
1075
+ t.Fatalf("finishLookupResponse compact = %d, want 28", n)
1076
+ }
1077
+ var badFinish [80]byte
1078
+ ne.PutUint32(badFinish[16:20], 40)
1079
+ ne.PutUint32(badFinish[24:28], 32)
1080
+ if n := finishLookupResponse(badFinish[:], 16, 2, 48, 7); n != 0 {
1081
+ t.Fatalf("finishLookupResponse bad second offset = %d, want 0", n)
1082
+ }
1083
+ if n := finishLookupResponse(badFinish[:], maxIntValue(), 1, 0, 7); n != 0 {
1084
+ t.Fatalf("finishLookupResponse header overflow = %d, want 0", n)
1085
+ }
1086
+}
1087
+
1088
+func TestLookupDecodeAndItemErrorCoverage(t *testing.T) {
1089
+ if _, err := DecodeCgroupsLookupResponse(make([]byte, CgroupsLookupRespHdr-1)); err != ErrTruncated {
1090
+ t.Fatalf("short cgroups response error = %v, want ErrTruncated", err)
1091
+ }
1092
+ cgResp := make([]byte, CgroupsLookupRespHdr+LookupDirEntrySize)
1093
+ ne.PutUint16(cgResp[0:2], 1)
1094
+ ne.PutUint32(cgResp[4:8], 1)
1095
+ ne.PutUint32(cgResp[CgroupsLookupRespHdr+4:CgroupsLookupRespHdr+8], CgroupsLookupItemHdr)
1096
+ if _, err := DecodeCgroupsLookupResponse(cgResp[:CgroupsLookupRespHdr]); err != ErrTruncated {
1097
+ t.Fatalf("cgroups response truncated dir error = %v, want ErrTruncated", err)
1098
+ }
1099
+ if _, err := DecodeCgroupsLookupResponse(cgResp); err != ErrOutOfBounds {
1100
+ t.Fatalf("cgroups response missing item error = %v, want ErrOutOfBounds", err)
1101
+ }
1102
+ cgShortItem := append([]byte(nil), cgResp...)
1103
+ ne.PutUint32(cgShortItem[CgroupsLookupRespHdr+4:CgroupsLookupRespHdr+8], CgroupsLookupItemHdr-1)
1104
+ cgShortItem = append(cgShortItem, make([]byte, CgroupsLookupItemHdr-1)...)
1105
+ if _, err := DecodeCgroupsLookupResponse(cgShortItem); err != ErrBadLayout {
1106
+ t.Fatalf("cgroups response short item error = %v, want ErrBadLayout", err)
1107
+ }
1108
+ if _, err := (&CgroupsLookupResponseView{ItemCount: 1, payload: cgResp}).Item(0); err != ErrOutOfBounds {
1109
+ t.Fatalf("manual cgroups item error = %v, want ErrOutOfBounds", err)
1110
+ }
1111
+
1112
+ cgItem := validCgroupsLookupItemBytes(t)
1113
+ for _, tc := range []struct {
1114
+ name string
1115
+ edit func([]byte)
1116
+ want error
1117
+ }{
1118
+ {
1119
+ name: "short",
1120
+ edit: func(b []byte) {
1121
+ b = b[:CgroupsLookupItemHdr-1]
1122
+ copy(cgItem, b)
1123
+ },
1124
+ want: ErrTruncated,
1125
+ },
1126
+ {
1127
+ name: "bad reserved",
1128
+ edit: func(b []byte) {
1129
+ ne.PutUint16(b[6:8], 1)
1130
+ },
1131
+ want: ErrBadLayout,
1132
+ },
1133
+ {
1134
+ name: "path below header",
1135
+ edit: func(b []byte) {
1136
+ ne.PutUint32(b[8:12], 0)
1137
+ },
1138
+ want: ErrOutOfBounds,
1139
+ },
1140
+ {
1141
+ name: "name below header",
1142
+ edit: func(b []byte) {
1143
+ ne.PutUint32(b[16:20], 0)
1144
+ },
1145
+ want: ErrOutOfBounds,
1146
+ },
1147
+ {
1148
+ name: "label table missing",
1149
+ edit: func(b []byte) {
1150
+ ne.PutUint16(b[24:26], 2)
1151
+ },
1152
+ want: ErrOutOfBounds,
1153
+ },
1154
+ } {
1155
+ item := validCgroupsLookupItemBytes(t)
1156
+ if tc.name == "short" {
1157
+ if _, err := decodeCgroupsLookupItem(item[:CgroupsLookupItemHdr-1]); err != tc.want {
1158
+ t.Fatalf("decode cgroups item %s error = %v, want %v", tc.name, err, tc.want)
1159
+ }
1160
+ continue
1161
+ }
1162
+ tc.edit(item)
1163
+ if _, err := decodeCgroupsLookupItem(item); err != tc.want {
1164
+ t.Fatalf("decode cgroups item %s error = %v, want %v", tc.name, err, tc.want)
1165
+ }
1166
+ }
1167
+
1168
+ if _, err := DecodeAppsLookupResponse(make([]byte, AppsLookupRespHdr-1)); err != ErrTruncated {
1169
+ t.Fatalf("short apps response error = %v, want ErrTruncated", err)
1170
+ }
1171
+ appsResp := make([]byte, AppsLookupRespHdr+LookupDirEntrySize)
1172
+ ne.PutUint16(appsResp[0:2], 1)
1173
+ ne.PutUint32(appsResp[4:8], 1)
1174
+ ne.PutUint32(appsResp[AppsLookupRespHdr+4:AppsLookupRespHdr+8], AppsLookupItemHdr)
1175
+ if _, err := DecodeAppsLookupResponse(appsResp[:AppsLookupRespHdr]); err != ErrTruncated {
1176
+ t.Fatalf("apps response truncated dir error = %v, want ErrTruncated", err)
1177
+ }
1178
+ if _, err := DecodeAppsLookupResponse(appsResp); err != ErrOutOfBounds {
1179
+ t.Fatalf("apps response missing item error = %v, want ErrOutOfBounds", err)
1180
+ }
1181
+ appsShortItem := append([]byte(nil), appsResp...)
1182
+ ne.PutUint32(appsShortItem[AppsLookupRespHdr+4:AppsLookupRespHdr+8], AppsLookupItemHdr-1)
1183
+ appsShortItem = append(appsShortItem, make([]byte, AppsLookupItemHdr-1)...)
1184
+ if _, err := DecodeAppsLookupResponse(appsShortItem); err != ErrBadLayout {
1185
+ t.Fatalf("apps response short item error = %v, want ErrBadLayout", err)
1186
+ }
1187
+ if _, err := (&AppsLookupResponseView{ItemCount: 1, payload: appsResp}).Item(0); err != ErrOutOfBounds {
1188
+ t.Fatalf("manual apps item error = %v, want ErrOutOfBounds", err)
1189
+ }
1190
+
1191
+ for _, tc := range []struct {
1192
+ name string
1193
+ edit func([]byte)
1194
+ want error
1195
+ }{
1196
+ {
1197
+ name: "short",
1198
+ edit: nil,
1199
+ want: ErrTruncated,
1200
+ },
1201
+ {
1202
+ name: "bad reserved",
1203
+ edit: func(b []byte) {
1204
+ ne.PutUint32(b[20:24], 1)
1205
+ },
1206
+ want: ErrBadLayout,
1207
+ },
1208
+ {
1209
+ name: "comm below header",
1210
+ edit: func(b []byte) {
1211
+ ne.PutUint32(b[32:36], 0)
1212
+ },
1213
+ want: ErrOutOfBounds,
1214
+ },
1215
+ {
1216
+ name: "path below header",
1217
+ edit: func(b []byte) {
1218
+ ne.PutUint32(b[40:44], 0)
1219
+ },
1220
+ want: ErrOutOfBounds,
1221
+ },
1222
+ {
1223
+ name: "name below header",
1224
+ edit: func(b []byte) {
1225
+ ne.PutUint32(b[48:52], 0)
1226
+ },
1227
+ want: ErrOutOfBounds,
1228
+ },
1229
+ {
1230
+ name: "retry with orchestrator",
1231
+ edit: func(b []byte) {
1232
+ ne.PutUint16(b[6:8], AppsCgroupUnknownRetryLater)
1233
+ },
1234
+ want: ErrBadLayout,
1235
+ },
1236
+ {
1237
+ name: "permanent missing path",
1238
+ edit: func(b []byte) {
1239
+ ne.PutUint16(b[4:6], 0)
1240
+ ne.PutUint16(b[6:8], AppsCgroupUnknownPermanent)
1241
+ ne.PutUint32(b[44:48], 0)
1242
+ ne.PutUint32(b[52:56], 0)
1243
+ ne.PutUint16(b[56:58], 0)
1244
+ },
1245
+ want: ErrBadLayout,
1246
+ },
1247
+ {
1248
+ name: "label table missing",
1249
+ edit: func(b []byte) {
1250
+ ne.PutUint16(b[56:58], 2)
1251
+ },
1252
+ want: ErrOutOfBounds,
1253
+ },
1254
+ } {
1255
+ item := validAppsLookupItemBytes(t)
1256
+ if tc.name == "short" {
1257
+ if _, err := decodeAppsLookupItem(item[:AppsLookupItemHdr-1]); err != tc.want {
1258
+ t.Fatalf("decode apps item %s error = %v, want %v", tc.name, err, tc.want)
1259
+ }
1260
+ continue
1261
+ }
1262
+ tc.edit(item)
1263
+ if _, err := decodeAppsLookupItem(item); err != tc.want {
1264
+ t.Fatalf("decode apps item %s error = %v, want %v", tc.name, err, tc.want)
1265
+ }
1266
+ }
1267
+ _ = cgItem
1268
+}
1269
+
1270
+func TestLookupResponseDecodeValidationCoverage(t *testing.T) {
1271
+ var resp [1024]byte
1272
+
1273
+ cg := NewCgroupsLookupBuilder(resp[:], 1, 0)
1274
+ if err := cg.Add(CgroupLookupKnown, OrchestratorDocker, []byte("/x"), []byte("name"), nil); err != nil {
1275
+ t.Fatalf("add cgroups response: %v", err)
1276
+ }
1277
+ cgTotal := cg.Finish()
1278
+ cgItem := cgroupsLookupItemStart(t, resp[:])
1279
+
1280
+ for _, tc := range []struct {
1281
+ name string
1282
+ edit func([]byte)
1283
+ want error
1284
+ }{
1285
+ {
1286
+ name: "bad flags",
1287
+ edit: func(b []byte) {
1288
+ ne.PutUint16(b[2:4], 1)
1289
+ },
1290
+ want: ErrBadLayout,
1291
+ },
1292
+ {
1293
+ name: "bad item layout",
1294
+ edit: func(b []byte) {
1295
+ ne.PutUint16(b[cgItem:cgItem+2], 2)
1296
+ },
1297
+ want: ErrBadLayout,
1298
+ },
1299
+ {
1300
+ name: "empty path",
1301
+ edit: func(b []byte) {
1302
+ ne.PutUint32(b[cgItem+12:cgItem+16], 0)
1303
+ },
1304
+ want: ErrBadLayout,
1305
+ },
1306
+ {
1307
+ name: "unknown with metadata",
1308
+ edit: func(b []byte) {
1309
+ ne.PutUint16(b[cgItem+2:cgItem+4], CgroupLookupUnknownRetryLater)
1310
+ },
1311
+ want: ErrBadLayout,
1312
+ },
1313
+ {
1314
+ name: "overlap",
1315
+ edit: func(b []byte) {
1316
+ ne.PutUint32(b[cgItem+16:cgItem+20], uint32(CgroupsLookupItemHdr+1))
1317
+ ne.PutUint32(b[cgItem+20:cgItem+24], 1)
1318
+ },
1319
+ want: ErrBadLayout,
1320
+ },
1321
+ } {
1322
+ bad := append([]byte(nil), resp[:cgTotal]...)
1323
+ tc.edit(bad)
1324
+ if _, err := DecodeCgroupsLookupResponse(bad); err != tc.want {
1325
+ t.Fatalf("decode cgroups response %s error = %v, want %v", tc.name, err, tc.want)
1326
+ }
1327
+ }
1328
+
1329
+ apps := NewAppsLookupBuilder(resp[:], 1, 0)
1330
+ if err := apps.Add(
1331
+ PidLookupKnown,
1332
+ AppsCgroupKnown,
1333
+ OrchestratorDocker,
1334
+ 1234, 1, 1000, 42,
1335
+ []byte("nginx"),
1336
+ []byte("/docker/abc"),
1337
+ []byte("container-a"),
1338
+ nil,
1339
+ ); err != nil {
1340
+ t.Fatalf("add apps response: %v", err)
1341
+ }
1342
+ appsTotal := apps.Finish()
1343
+ appsItem := appsLookupItemStart(t, resp[:])
1344
+
1345
+ for _, tc := range []struct {
1346
+ name string
1347
+ edit func([]byte)
1348
+ want error
1349
+ }{
1350
+ {
1351
+ name: "bad flags",
1352
+ edit: func(b []byte) {
1353
+ ne.PutUint16(b[2:4], 1)
1354
+ },
1355
+ want: ErrBadLayout,
1356
+ },
1357
+ {
1358
+ name: "bad item layout",
1359
+ edit: func(b []byte) {
1360
+ ne.PutUint16(b[appsItem:appsItem+2], 2)
1361
+ },
1362
+ want: ErrBadLayout,
1363
+ },
1364
+ {
1365
+ name: "bad status",
1366
+ edit: func(b []byte) {
1367
+ ne.PutUint16(b[appsItem+2:appsItem+4], 99)
1368
+ },
1369
+ want: ErrBadLayout,
1370
+ },
1371
+ {
1372
+ name: "bad cgroup status",
1373
+ edit: func(b []byte) {
1374
+ ne.PutUint16(b[appsItem+6:appsItem+8], 99)
1375
+ },
1376
+ want: ErrBadLayout,
1377
+ },
1378
+ {
1379
+ name: "comm too long",
1380
+ edit: func(b []byte) {
1381
+ ne.PutUint32(b[appsItem+36:appsItem+40], 16)
1382
+ },
1383
+ want: ErrBadLayout,
1384
+ },
1385
+ {
1386
+ name: "unknown with data",
1387
+ edit: func(b []byte) {
1388
+ ne.PutUint16(b[appsItem+2:appsItem+4], PidLookupUnknown)
1389
+ },
1390
+ want: ErrBadLayout,
1391
+ },
1392
+ {
1393
+ name: "known empty comm",
1394
+ edit: func(b []byte) {
1395
+ ne.PutUint32(b[appsItem+36:appsItem+40], 0)
1396
+ },
1397
+ want: ErrBadLayout,
1398
+ },
1399
+ {
1400
+ name: "known empty path",
1401
+ edit: func(b []byte) {
1402
+ ne.PutUint32(b[appsItem+44:appsItem+48], 0)
1403
+ },
1404
+ want: ErrBadLayout,
1405
+ },
1406
+ {
1407
+ name: "host root with metadata",
1408
+ edit: func(b []byte) {
1409
+ ne.PutUint16(b[appsItem+6:appsItem+8], AppsCgroupHostRoot)
1410
+ },
1411
+ want: ErrBadLayout,
1412
+ },
1413
+ {
1414
+ name: "overlap",
1415
+ edit: func(b []byte) {
1416
+ ne.PutUint32(b[appsItem+40:appsItem+44], uint32(AppsLookupItemHdr+1))
1417
+ ne.PutUint32(b[appsItem+44:appsItem+48], 4)
1418
+ },
1419
+ want: ErrBadLayout,
1420
+ },
1421
+ } {
1422
+ bad := append([]byte(nil), resp[:appsTotal]...)
1423
+ tc.edit(bad)
1424
+ if _, err := DecodeAppsLookupResponse(bad); err != tc.want {
1425
+ t.Fatalf("decode apps response %s error = %v, want %v", tc.name, err, tc.want)
1426
+ }
1427
+ }
1428
+}
1429
+
1430
+func TestLookupDispatchCoverage(t *testing.T) {
1431
+ var req [128]byte
1432
+ var resp [512]byte
1433
+
1434
+ reqLen, err := EncodeCgroupsLookupRequest([][]byte{[]byte("/x"), []byte("/y")}, req[:])
1435
+ if err != nil {
1436
+ t.Fatalf("encode cgroups dispatch request: %v", err)
1437
+ }
1438
+ n, err := DispatchCgroupsLookup(req[:reqLen], resp[:], func(request *CgroupsLookupRequestView, builder *CgroupsLookupBuilder) bool {
1439
+ for i := uint32(0); i < request.ItemCount; i++ {
1440
+ path, err := request.Item(i)
1441
+ if err != nil {
1442
+ return false
1443
+ }
1444
+ if err := builder.Add(CgroupLookupKnown, OrchestratorDocker, path.Bytes(), []byte("name"), nil); err != nil {
1445
+ return false
1446
+ }
1447
+ }
1448
+ return true
1449
+ })
1450
+ if err != nil || n == 0 {
1451
+ t.Fatalf("dispatch cgroups success = n %d err %v", n, err)
1452
+ }
1453
+ if _, err := DecodeCgroupsLookupResponse(resp[:n]); err != nil {
1454
+ t.Fatalf("decode dispatched cgroups response: %v", err)
1455
+ }
1456
+ n, err = DispatchCgroupsLookup(req[:reqLen], resp[:], func(*CgroupsLookupRequestView, *CgroupsLookupBuilder) bool {
1457
+ return false
1458
+ })
1459
+ if err != ErrBadLayout || n != 0 {
1460
+ t.Fatalf("dispatch cgroups handler false = n %d err %v", n, err)
1461
+ }
1462
+ n, err = DispatchCgroupsLookup(req[:reqLen], resp[:], func(*CgroupsLookupRequestView, *CgroupsLookupBuilder) bool {
1463
+ return true
1464
+ })
1465
+ if err != ErrBadItemCount || n != 0 {
1466
+ t.Fatalf("dispatch cgroups bad count = n %d err %v", n, err)
1467
+ }
1468
+ n, err = DispatchCgroupsLookup(req[:reqLen], resp[:], func(_ *CgroupsLookupRequestView, builder *CgroupsLookupBuilder) bool {
1469
+ _ = builder.Add(99, 0, []byte("/x"), nil, nil)
1470
+ return false
1471
+ })
1472
+ if err != ErrBadLayout || n != 0 {
1473
+ t.Fatalf("dispatch cgroups builder error = n %d err %v", n, err)
1474
+ }
1475
+ n, err = DispatchCgroupsLookup(req[:reqLen], resp[:], func(_ *CgroupsLookupRequestView, builder *CgroupsLookupBuilder) bool {
1476
+ _ = builder.Add(99, 0, []byte("/x"), nil, nil)
1477
+ return true
1478
+ })
1479
+ if err != ErrBadLayout || n != 0 {
1480
+ t.Fatalf("dispatch cgroups post-handler builder error = n %d err %v", n, err)
1481
+ }
1482
+ n, err = DispatchCgroupsLookup(req[:reqLen], resp[:], func(request *CgroupsLookupRequestView, builder *CgroupsLookupBuilder) bool {
1483
+ builder.itemCount = request.ItemCount
1484
+ builder.dataOffset = 0
1485
+ ne.PutUint32(builder.buf[CgroupsLookupRespHdr:CgroupsLookupRespHdr+4], 8)
1486
+ return true
1487
+ })
1488
+ if err != ErrOverflow || n != 0 {
1489
+ t.Fatalf("dispatch cgroups finish error = n %d err %v", n, err)
1490
+ }
1491
+ if n, err := DispatchCgroupsLookup(req[:CgroupsLookupReqHdr-1], resp[:], nil); err != ErrTruncated || n != 0 {
1492
+ t.Fatalf("dispatch cgroups bad request = n %d err %v", n, err)
1493
+ }
1494
+
1495
+ reqLen, err = EncodeAppsLookupRequest([]uint32{1, 2}, req[:])
1496
+ if err != nil {
1497
+ t.Fatalf("encode apps dispatch request: %v", err)
1498
+ }
1499
+ n, err = DispatchAppsLookup(req[:reqLen], resp[:], func(request *AppsLookupRequestView, builder *AppsLookupBuilder) bool {
1500
+ for i := uint32(0); i < request.ItemCount; i++ {
1501
+ pid, err := request.Item(i)
1502
+ if err != nil {
1503
+ return false
1504
+ }
1505
+ if err := builder.Add(PidLookupKnown, AppsCgroupHostRoot, 0, pid, 0, 0, 0, []byte("proc"), nil, nil, nil); err != nil {
1506
+ return false
1507
+ }
1508
+ }
1509
+ return true
1510
+ })
1511
+ if err != nil || n == 0 {
1512
+ t.Fatalf("dispatch apps success = n %d err %v", n, err)
1513
+ }
1514
+ if _, err := DecodeAppsLookupResponse(resp[:n]); err != nil {
1515
+ t.Fatalf("decode dispatched apps response: %v", err)
1516
+ }
1517
+ n, err = DispatchAppsLookup(req[:reqLen], resp[:], func(*AppsLookupRequestView, *AppsLookupBuilder) bool {
1518
+ return false
1519
+ })
1520
+ if err != ErrBadLayout || n != 0 {
1521
+ t.Fatalf("dispatch apps handler false = n %d err %v", n, err)
1522
+ }
1523
+ n, err = DispatchAppsLookup(req[:reqLen], resp[:], func(*AppsLookupRequestView, *AppsLookupBuilder) bool {
1524
+ return true
1525
+ })
1526
+ if err != ErrBadItemCount || n != 0 {
1527
+ t.Fatalf("dispatch apps bad count = n %d err %v", n, err)
1528
+ }
1529
+ n, err = DispatchAppsLookup(req[:reqLen], resp[:], func(_ *AppsLookupRequestView, builder *AppsLookupBuilder) bool {
1530
+ _ = builder.Add(99, 0, 0, 1, 0, 0, 0, []byte("x"), nil, nil, nil)
1531
+ return false
1532
+ })
1533
+ if err != ErrBadLayout || n != 0 {
1534
+ t.Fatalf("dispatch apps builder error = n %d err %v", n, err)
1535
+ }
1536
+ n, err = DispatchAppsLookup(req[:reqLen], resp[:], func(_ *AppsLookupRequestView, builder *AppsLookupBuilder) bool {
1537
+ _ = builder.Add(99, 0, 0, 1, 0, 0, 0, []byte("x"), nil, nil, nil)
1538
+ return true
1539
+ })
1540
+ if err != ErrBadLayout || n != 0 {
1541
+ t.Fatalf("dispatch apps post-handler builder error = n %d err %v", n, err)
1542
+ }
1543
+ n, err = DispatchAppsLookup(req[:reqLen], resp[:], func(request *AppsLookupRequestView, builder *AppsLookupBuilder) bool {
1544
+ builder.itemCount = request.ItemCount
1545
+ builder.dataOffset = 0
1546
+ ne.PutUint32(builder.buf[AppsLookupRespHdr:AppsLookupRespHdr+4], 8)
1547
+ return true
1548
+ })
1549
+ if err != ErrOverflow || n != 0 {
1550
+ t.Fatalf("dispatch apps finish error = n %d err %v", n, err)
1551
+ }
1552
+ if n, err := DispatchAppsLookup(req[:AppsLookupReqHdr-1], resp[:], nil); err != ErrTruncated || n != 0 {
1553
+ t.Fatalf("dispatch apps bad request = n %d err %v", n, err)
1554
+ }
1555
+}
src/go/pkg/netipc/protocol/string_reverse.go
+23
-7
@@ -21,8 +21,12 @@ func StringReverseEncode(s string, buf []byte) int {
21
if len(buf) < total {
22
return 0
23
}
24
+ strLen, ok := checkedU32Int(len(s))
25
+ if !ok {
26
+ return 0
27
+ }
28
ne.PutUint32(buf[0:4], uint32(StringReverseHdrSize)) // str_offset
25
- ne.PutUint32(buf[4:8], uint32(len(s))) // str_length
29
+ ne.PutUint32(buf[4:8], strLen) // str_length
30
if len(s) > 0 {
31
copy(buf[8:8+len(s)], s)
32
}
@@ -35,17 +39,29 @@ func StringReverseDecode(buf []byte) (StringReverseView, error) {
39
if len(buf) < StringReverseHdrSize {
40
return StringReverseView{}, ErrTruncated
41
}
38
- strOffset := int(ne.Uint32(buf[0:4]))
39
- strLength := int(ne.Uint32(buf[4:8]))
40
- if strOffset+strLength+1 > len(buf) {
42
+ strOffset, err := checkedWireU32Int(buf, 0)
43
+ if err != nil {
44
+ return StringReverseView{}, err
45
+ }
46
+ strLength, err := checkedWireU32Int(buf, 4)
47
+ if err != nil {
48
+ return StringReverseView{}, err
49
+ }
50
+ strLength32 := ne.Uint32(buf[4:8])
51
+ strEnd, ok := checkedAddInt(strOffset, strLength)
52
+ if !ok {
53
+ return StringReverseView{}, ErrOutOfBounds
54
+ }
55
+ strNulEnd, ok := checkedAddInt(strEnd, 1)
56
+ if !ok || strNulEnd > len(buf) {
57
return StringReverseView{}, ErrOutOfBounds
58
}
43
- if buf[strOffset+strLength] != 0 {
59
+ if buf[strEnd] != 0 {
60
return StringReverseView{}, ErrMissingNul
61
}
62
return StringReverseView{
47
- Str: string(buf[strOffset : strOffset+strLength]),
48
- StrLen: uint32(strLength),
63
+ Str: string(buf[strOffset:strEnd]),
64
+ StrLen: strLength32,
65
}, nil
66
}
67
src/go/pkg/netipc/service/apps_lookup/client.go
new
+16
@@ -0,0 +1,16 @@
1
+//go:build unix
2
+
3
+package apps_lookup
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
+)
9
+
10
+func clientConfigToTransport(config ClientConfig) posix.ClientConfig {
11
+ return transportconfig.PosixClient(transportconfig.TypedConfig(config))
12
+}
13
+
14
+func serverConfigToTransport(config ServerConfig) posix.ServerConfig {
15
+ return transportconfig.PosixServer(transportconfig.TypedConfig(config))
16
+}
src/go/pkg/netipc/service/apps_lookup/client_common.go
new
+39
@@ -0,0 +1,39 @@
1
+package apps_lookup
2
+
3
+import (
4
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
5
+ raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
6
+)
7
+
8
+type Client struct {
9
+ inner *raw.Client
10
+}
11
+
12
+func NewClient(runDir, serviceName string, config ClientConfig) *Client {
13
+ return &Client{inner: raw.NewAppsLookupClient(runDir, serviceName, clientConfigToTransport(config))}
14
+}
15
+
16
+func (c *Client) Refresh() bool { return c.inner.Refresh() }
17
+func (c *Client) Ready() bool { return c.inner.Ready() }
18
+func (c *Client) Status() ClientStatus {
19
+ return c.inner.Status()
20
+}
21
+func (c *Client) Call(pids []uint32) (*protocol.AppsLookupResponseView, error) {
22
+ return c.inner.CallAppsLookup(pids)
23
+}
24
+func (c *Client) Close() { c.inner.Close() }
25
+
26
+type Server struct {
27
+ inner *raw.Server
28
+}
29
+
30
+func NewServer(runDir, serviceName string, config ServerConfig, handler Handler) *Server {
31
+ return &Server{inner: raw.NewServer(runDir, serviceName, serverConfigToTransport(config), protocol.MethodAppsLookup, raw.AppsLookupDispatch(handler.Handle))}
32
+}
33
+
34
+func NewServerWithWorkers(runDir, serviceName string, config ServerConfig, handler Handler, workerCount int) *Server {
35
+ return &Server{inner: raw.NewServerWithWorkers(runDir, serviceName, serverConfigToTransport(config), protocol.MethodAppsLookup, raw.AppsLookupDispatch(handler.Handle), workerCount)}
36
+}
37
+
38
+func (s *Server) Run() error { return s.inner.Run() }
39
+func (s *Server) Stop() { s.inner.Stop() }
src/go/pkg/netipc/service/apps_lookup/client_windows.go
new
+16
@@ -0,0 +1,16 @@
1
+//go:build windows
2
+
3
+package apps_lookup
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
8
+)
9
+
10
+func clientConfigToTransport(config ClientConfig) windows.ClientConfig {
11
+ return transportconfig.WindowsClient(transportconfig.TypedConfig(config))
12
+}
13
+
14
+func serverConfigToTransport(config ServerConfig) windows.ServerConfig {
15
+ return transportconfig.WindowsServer(transportconfig.TypedConfig(config))
16
+}
src/go/pkg/netipc/service/apps_lookup/types.go
new
+33
@@ -0,0 +1,33 @@
1
+// Package apps_lookup provides the public single-kind L2 surface for the
2
+// apps-lookup service.
3
+package apps_lookup
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
8
+ raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
9
+)
10
+
11
+type ClientState = raw.ClientState
12
+
13
+const (
14
+ StateDisconnected = raw.StateDisconnected
15
+ StateConnecting = raw.StateConnecting
16
+ StateReady = raw.StateReady
17
+ StateNotFound = raw.StateNotFound
18
+ StateAuthFailed = raw.StateAuthFailed
19
+ StateIncompatible = raw.StateIncompatible
20
+ StateBroken = raw.StateBroken
21
+)
22
+
23
+type ClientStatus = raw.ClientStatus
24
+
25
+type ClientConfig transportconfig.TypedConfig
26
+
27
+type ServerConfig transportconfig.TypedConfig
28
+
29
+type HandlerFunc = func(*protocol.AppsLookupRequestView, *protocol.AppsLookupBuilder) bool
30
+
31
+type Handler struct {
32
+ Handle HandlerFunc
33
+}
src/go/pkg/netipc/service/cgroups/cache_windows.go
deleted
-42
@@ -1,42 +0,0 @@
1
-//go:build windows
2
-
3
-package cgroups
4
-
5
-import (
6
- raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
7
-)
8
-
9
-// Cache is the public L3 client-side cgroups snapshot cache.
10
-type Cache struct {
11
- inner *raw.Cache
12
-}
13
-
14
-// NewCache creates a new L3 cache. Does NOT connect.
15
-func NewCache(runDir, serviceName string, config ClientConfig) *Cache {
16
- return &Cache{inner: raw.NewCache(runDir, serviceName, clientConfigToTransport(config))}
17
-}
18
-
19
-// Refresh drives the L2 client and requests a fresh snapshot.
20
-func (c *Cache) Refresh() bool {
21
- return c.inner.Refresh()
22
-}
23
-
24
-// Ready returns true if at least one successful refresh has occurred.
25
-func (c *Cache) Ready() bool {
26
- return c.inner.Ready()
27
-}
28
-
29
-// Lookup finds a cached item by hash + name. O(1), no I/O.
30
-func (c *Cache) Lookup(hash uint32, name string) (CacheItem, bool) {
31
- return c.inner.Lookup(hash, name)
32
-}
33
-
34
-// Status returns a diagnostic snapshot for the L3 cache.
35
-func (c *Cache) Status() CacheStatus {
36
- return c.inner.Status()
37
-}
38
-
39
-// Close frees all cached items and closes the L2 client.
40
-func (c *Cache) Close() {
41
- c.inner.Close()
42
-}
src/go/pkg/netipc/service/cgroups/client_windows.go
deleted
-113
@@ -1,113 +0,0 @@
1
-//go:build windows
2
-
3
-package cgroups
4
-
5
-import (
6
- "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
- raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
8
- windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
9
-)
10
-
11
-func snapshotDispatch(handler Handler) raw.DispatchHandler {
12
- return raw.SnapshotDispatch(handler.Handle, handler.SnapshotMaxItems)
13
-}
14
-
15
-func clientConfigToTransport(config ClientConfig) windows.ClientConfig {
16
- return windows.ClientConfig{
17
- SupportedProfiles: config.SupportedProfiles,
18
- PreferredProfiles: config.PreferredProfiles,
19
- MaxRequestBatchItems: config.MaxRequestBatchItems,
20
- MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
21
- MaxResponseBatchItems: config.MaxRequestBatchItems,
22
- AuthToken: config.AuthToken,
23
- }
24
-}
25
-
26
-func serverConfigToTransport(config ServerConfig) windows.ServerConfig {
27
- return windows.ServerConfig{
28
- SupportedProfiles: config.SupportedProfiles,
29
- PreferredProfiles: config.PreferredProfiles,
30
- MaxRequestBatchItems: config.MaxRequestBatchItems,
31
- MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
32
- MaxResponseBatchItems: config.MaxRequestBatchItems,
33
- AuthToken: config.AuthToken,
34
- }
35
-}
36
-
37
-// Client is the public L2 client context for the cgroups-snapshot service.
38
-type Client struct {
39
- inner *raw.Client
40
-}
41
-
42
-// NewClient creates a new client context. Does NOT connect.
43
-func NewClient(runDir, serviceName string, config ClientConfig) *Client {
44
- return &Client{inner: raw.NewSnapshotClient(runDir, serviceName, clientConfigToTransport(config))}
45
-}
46
-
47
-// Refresh attempts connect if DISCONNECTED/NOT_FOUND, reconnect if BROKEN.
48
-func (c *Client) Refresh() bool {
49
- return c.inner.Refresh()
50
-}
51
-
52
-// Ready returns true only if the client is in the READY state.
53
-func (c *Client) Ready() bool {
54
- return c.inner.Ready()
55
-}
56
-
57
-// Status returns a diagnostic counters snapshot.
58
-func (c *Client) Status() ClientStatus {
59
- return c.inner.Status()
60
-}
61
-
62
-// CallSnapshot performs a blocking typed cgroups snapshot call.
63
-func (c *Client) CallSnapshot() (*protocol.CgroupsResponseView, error) {
64
- return c.inner.CallSnapshot()
65
-}
66
-
67
-// Close tears down the connection and releases resources.
68
-func (c *Client) Close() {
69
- c.inner.Close()
70
-}
71
-
72
-// Server is the public managed server for the cgroups-snapshot service kind.
73
-type Server struct {
74
- inner *raw.Server
75
-}
76
-
77
-// NewServer creates a new managed server.
78
-func NewServer(runDir, serviceName string, config ServerConfig, handler Handler) *Server {
79
- return &Server{
80
- inner: raw.NewServer(
81
- runDir,
82
- serviceName,
83
- serverConfigToTransport(config),
84
- protocol.MethodCgroupsSnapshot,
85
- snapshotDispatch(handler),
86
- ),
87
- }
88
-}
89
-
90
-// NewServerWithWorkers creates a server with an explicit worker count limit.
91
-func NewServerWithWorkers(runDir, serviceName string, config ServerConfig,
92
- handler Handler, workerCount int) *Server {
93
- return &Server{
94
- inner: raw.NewServerWithWorkers(
95
- runDir,
96
- serviceName,
97
- serverConfigToTransport(config),
98
- protocol.MethodCgroupsSnapshot,
99
- snapshotDispatch(handler),
100
- workerCount,
101
- ),
102
- }
103
-}
104
-
105
-// Run starts the acceptor loop. Blocking.
106
-func (s *Server) Run() error {
107
- return s.inner.Run()
108
-}
109
-
110
-// Stop signals the server to stop.
111
-func (s *Server) Stop() {
112
- s.inner.Stop()
113
-}
src/go/pkg/netipc/service/cgroups/compat.go
new
+35
@@ -0,0 +1,35 @@
1
+// Package cgroups preserves the historical import path for the
2
+// cgroups-snapshot service.
3
+//
4
+// New code should prefer package cgroups_snapshot.
5
+package cgroups
6
+
7
+import snapshot "github.com/netdata/netdata/go/plugins/pkg/netipc/service/cgroups_snapshot"
8
+
9
+type ClientState = snapshot.ClientState
10
+
11
+const (
12
+ StateDisconnected = snapshot.StateDisconnected
13
+ StateConnecting = snapshot.StateConnecting
14
+ StateReady = snapshot.StateReady
15
+ StateNotFound = snapshot.StateNotFound
16
+ StateAuthFailed = snapshot.StateAuthFailed
17
+ StateIncompatible = snapshot.StateIncompatible
18
+ StateBroken = snapshot.StateBroken
19
+)
20
+
21
+type ClientStatus = snapshot.ClientStatus
22
+type ClientConfig = snapshot.ClientConfig
23
+type ServerConfig = snapshot.ServerConfig
24
+type SnapshotHandler = snapshot.SnapshotHandler
25
+type Handler = snapshot.Handler
26
+type Client = snapshot.Client
27
+type Server = snapshot.Server
28
+type Cache = snapshot.Cache
29
+type CacheItem = snapshot.CacheItem
30
+type CacheStatus = snapshot.CacheStatus
31
+
32
+var NewClient = snapshot.NewClient
33
+var NewServer = snapshot.NewServer
34
+var NewServerWithWorkers = snapshot.NewServerWithWorkers
35
+var NewCache = snapshot.NewCache
src/go/pkg/netipc/service/cgroups_lookup/client.go
new
+16
@@ -0,0 +1,16 @@
1
+//go:build unix
2
+
3
+package cgroups_lookup
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
+)
9
+
10
+func clientConfigToTransport(config ClientConfig) posix.ClientConfig {
11
+ return transportconfig.PosixClient(transportconfig.TypedConfig(config))
12
+}
13
+
14
+func serverConfigToTransport(config ServerConfig) posix.ServerConfig {
15
+ return transportconfig.PosixServer(transportconfig.TypedConfig(config))
16
+}
src/go/pkg/netipc/service/cgroups_lookup/client_common.go
new
+39
@@ -0,0 +1,39 @@
1
+package cgroups_lookup
2
+
3
+import (
4
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
5
+ raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
6
+)
7
+
8
+type Client struct {
9
+ inner *raw.Client
10
+}
11
+
12
+func NewClient(runDir, serviceName string, config ClientConfig) *Client {
13
+ return &Client{inner: raw.NewCgroupsLookupClient(runDir, serviceName, clientConfigToTransport(config))}
14
+}
15
+
16
+func (c *Client) Refresh() bool { return c.inner.Refresh() }
17
+func (c *Client) Ready() bool { return c.inner.Ready() }
18
+func (c *Client) Status() ClientStatus {
19
+ return c.inner.Status()
20
+}
21
+func (c *Client) Call(paths [][]byte) (*protocol.CgroupsLookupResponseView, error) {
22
+ return c.inner.CallCgroupsLookup(paths)
23
+}
24
+func (c *Client) Close() { c.inner.Close() }
25
+
26
+type Server struct {
27
+ inner *raw.Server
28
+}
29
+
30
+func NewServer(runDir, serviceName string, config ServerConfig, handler Handler) *Server {
31
+ return &Server{inner: raw.NewServer(runDir, serviceName, serverConfigToTransport(config), protocol.MethodCgroupsLookup, raw.CgroupsLookupDispatch(handler.Handle))}
32
+}
33
+
34
+func NewServerWithWorkers(runDir, serviceName string, config ServerConfig, handler Handler, workerCount int) *Server {
35
+ return &Server{inner: raw.NewServerWithWorkers(runDir, serviceName, serverConfigToTransport(config), protocol.MethodCgroupsLookup, raw.CgroupsLookupDispatch(handler.Handle), workerCount)}
36
+}
37
+
38
+func (s *Server) Run() error { return s.inner.Run() }
39
+func (s *Server) Stop() { s.inner.Stop() }
src/go/pkg/netipc/service/cgroups_lookup/client_windows.go
new
+16
@@ -0,0 +1,16 @@
1
+//go:build windows
2
+
3
+package cgroups_lookup
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
8
+)
9
+
10
+func clientConfigToTransport(config ClientConfig) windows.ClientConfig {
11
+ return transportconfig.WindowsClient(transportconfig.TypedConfig(config))
12
+}
13
+
14
+func serverConfigToTransport(config ServerConfig) windows.ServerConfig {
15
+ return transportconfig.WindowsServer(transportconfig.TypedConfig(config))
16
+}
src/go/pkg/netipc/service/cgroups_lookup/types.go
new
+33
@@ -0,0 +1,33 @@
1
+// Package cgroups_lookup provides the public single-kind L2 surface for the
2
+// cgroups-lookup service.
3
+package cgroups_lookup
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
8
+ raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
9
+)
10
+
11
+type ClientState = raw.ClientState
12
+
13
+const (
14
+ StateDisconnected = raw.StateDisconnected
15
+ StateConnecting = raw.StateConnecting
16
+ StateReady = raw.StateReady
17
+ StateNotFound = raw.StateNotFound
18
+ StateAuthFailed = raw.StateAuthFailed
19
+ StateIncompatible = raw.StateIncompatible
20
+ StateBroken = raw.StateBroken
21
+)
22
+
23
+type ClientStatus = raw.ClientStatus
24
+
25
+type ClientConfig transportconfig.TypedConfig
26
+
27
+type ServerConfig transportconfig.TypedConfig
28
+
29
+type HandlerFunc = func(*protocol.CgroupsLookupRequestView, *protocol.CgroupsLookupBuilder) bool
30
+
31
+type Handler struct {
32
+ Handle HandlerFunc
33
+}
src/go/pkg/netipc/service/cgroups_snapshot/cache.go
new
+12
@@ -0,0 +1,12 @@
1
+//go:build unix
2
+
3
+package cgroups_snapshot
4
+
5
+import (
6
+ raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
7
+)
8
+
9
+// NewCache creates a new L3 cache. Does NOT connect.
10
+func NewCache(runDir, serviceName string, config ClientConfig) *Cache {
11
+ return &Cache{inner: raw.NewCache(runDir, serviceName, clientConfigToTransport(config))}
12
+}
src/go/pkg/netipc/service/cgroups_snapshot/cache_common.go
renamed
+1
-8
@@ -1,6 +1,4 @@
1
-//go:build unix
2
-
3
-package cgroups
1
+package cgroups_snapshot
2
3
import (
4
raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
@@ -11,11 +9,6 @@ type Cache struct {
9
inner *raw.Cache
10
}
11
14
-// NewCache creates a new L3 cache. Does NOT connect.
15
-func NewCache(runDir, serviceName string, config ClientConfig) *Cache {
16
- return &Cache{inner: raw.NewCache(runDir, serviceName, clientConfigToTransport(config))}
17
-}
18
-
12
// Refresh drives the L2 client and requests a fresh snapshot.
13
func (c *Cache) Refresh() bool {
14
return c.inner.Refresh()
src/go/pkg/netipc/service/cgroups_snapshot/cache_windows.go
new
+12
@@ -0,0 +1,12 @@
1
+//go:build windows
2
+
3
+package cgroups_snapshot
4
+
5
+import (
6
+ raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
7
+)
8
+
9
+// NewCache creates a new L3 cache. Does NOT connect.
10
+func NewCache(runDir, serviceName string, config ClientConfig) *Cache {
11
+ return &Cache{inner: raw.NewCache(runDir, serviceName, clientConfigToTransport(config))}
12
+}
src/go/pkg/netipc/service/cgroups_snapshot/cgroups_unix_test.go
renamed
+18
-1
@@ -1,6 +1,6 @@
1
//go:build unix
2
3
-package cgroups
3
+package cgroups_snapshot
4
5
import (
6
"fmt"
@@ -201,3 +201,20 @@ func TestClientNotReadyReturnsErrorUnix(t *testing.T) {
201
t.Fatalf("CallSnapshot err = %v, want %v", err, protocol.ErrBadLayout)
202
}
203
}
204
+
205
+func TestClientStatusAndServerWorkersUnix(t *testing.T) {
206
+ service := uniqueUnixService("status_workers")
207
+ cleanupUnix(service)
208
+
209
+ client := NewClient(testRunDirUnix, service, testUnixClientConfig())
210
+ defer client.Close()
211
+ if status := client.Status(); status.State == 0 && status.ConnectCount != 0 {
212
+ t.Fatalf("unexpected initial status: %+v", status)
213
+ }
214
+
215
+ server := NewServerWithWorkers(testRunDirUnix, service, testUnixServerConfig(), testUnixHandler(), 2)
216
+ if server == nil || server.inner == nil {
217
+ t.Fatal("NewServerWithWorkers returned nil server")
218
+ }
219
+ server.Stop()
220
+}
src/go/pkg/netipc/service/cgroups_snapshot/cgroups_windows_test.go
renamed
+1
-1
@@ -1,6 +1,6 @@
1
//go:build windows
2
3
-package cgroups
3
+package cgroups_snapshot
4
5
import (
6
"fmt"
src/go/pkg/netipc/service/cgroups_snapshot/client.go
new
+16
@@ -0,0 +1,16 @@
1
+//go:build unix
2
+
3
+package cgroups_snapshot
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
+)
9
+
10
+func clientConfigToTransport(config ClientConfig) posix.ClientConfig {
11
+ return transportconfig.PosixClient(transportconfig.TypedConfig(config))
12
+}
13
+
14
+func serverConfigToTransport(config ServerConfig) posix.ServerConfig {
15
+ return transportconfig.PosixServer(transportconfig.TypedConfig(config))
16
+}
src/go/pkg/netipc/service/cgroups_snapshot/client_common.go
renamed
+1
-26
@@ -1,39 +1,14 @@
1
-//go:build unix
2
-
3
-package cgroups
1
+package cgroups_snapshot
2
3
import (
4
"github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
5
raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
8
- "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
6
)
7
8
func snapshotDispatch(handler Handler) raw.DispatchHandler {
9
return raw.SnapshotDispatch(handler.Handle, handler.SnapshotMaxItems)
10
}
11
15
-func clientConfigToTransport(config ClientConfig) posix.ClientConfig {
16
- return posix.ClientConfig{
17
- SupportedProfiles: config.SupportedProfiles,
18
- PreferredProfiles: config.PreferredProfiles,
19
- MaxRequestBatchItems: config.MaxRequestBatchItems,
20
- MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
21
- MaxResponseBatchItems: config.MaxRequestBatchItems,
22
- AuthToken: config.AuthToken,
23
- }
24
-}
25
-
26
-func serverConfigToTransport(config ServerConfig) posix.ServerConfig {
27
- return posix.ServerConfig{
28
- SupportedProfiles: config.SupportedProfiles,
29
- PreferredProfiles: config.PreferredProfiles,
30
- MaxRequestBatchItems: config.MaxRequestBatchItems,
31
- MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
32
- MaxResponseBatchItems: config.MaxRequestBatchItems,
33
- AuthToken: config.AuthToken,
34
- }
35
-}
36
-
12
// Client is the public L2 client context for the cgroups-snapshot service.
13
type Client struct {
14
inner *raw.Client
src/go/pkg/netipc/service/cgroups_snapshot/client_windows.go
new
+16
@@ -0,0 +1,16 @@
1
+//go:build windows
2
+
3
+package cgroups_snapshot
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
7
+ windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
8
+)
9
+
10
+func clientConfigToTransport(config ClientConfig) windows.ClientConfig {
11
+ return transportconfig.WindowsClient(transportconfig.TypedConfig(config))
12
+}
13
+
14
+func serverConfigToTransport(config ServerConfig) windows.ServerConfig {
15
+ return transportconfig.WindowsServer(transportconfig.TypedConfig(config))
16
+}
src/go/pkg/netipc/service/cgroups_snapshot/types.go
renamed
+4
-15
@@ -4,10 +4,11 @@
4
// Clients connect to a service kind, not to a plugin identity. One service
5
// endpoint serves one request kind only. The outer request code remains part
6
// of the envelope for validation, not public multi-method dispatch.
7
-package cgroups
7
+package cgroups_snapshot
8
9
import (
10
"github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
11
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/service/internal/transportconfig"
12
raw "github.com/netdata/netdata/go/plugins/pkg/netipc/service/raw"
13
)
14
@@ -31,25 +32,13 @@ type ClientStatus = raw.ClientStatus
32
// cgroups-snapshot service.
33
//
34
// Transport-only tuning stays below the public typed API.
34
-type ClientConfig struct {
35
- SupportedProfiles uint32
36
- PreferredProfiles uint32
37
- MaxRequestBatchItems uint32
38
- MaxResponsePayloadBytes uint32
39
- AuthToken uint64
40
-}
35
+type ClientConfig transportconfig.TypedConfig
36
37
// ServerConfig is the public typed-server configuration for the
38
// cgroups-snapshot service.
39
//
40
// Transport-only tuning stays below the public typed API.
46
-type ServerConfig struct {
47
- SupportedProfiles uint32
48
- PreferredProfiles uint32
49
- MaxRequestBatchItems uint32
50
- MaxResponsePayloadBytes uint32
51
- AuthToken uint64
52
-}
41
+type ServerConfig transportconfig.TypedConfig
42
43
// SnapshotHandler is the typed callback used by the cgroups-snapshot service.
44
type SnapshotHandler = func(*protocol.CgroupsRequest, *protocol.CgroupsBuilder) bool
src/go/pkg/netipc/service/internal/transportconfig/posix.go
new
+27
@@ -0,0 +1,27 @@
1
+//go:build unix
2
+
3
+package transportconfig
4
+
5
+import "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
6
+
7
+func PosixClient(config TypedConfig) posix.ClientConfig {
8
+ return posix.ClientConfig{
9
+ SupportedProfiles: config.SupportedProfiles,
10
+ PreferredProfiles: config.PreferredProfiles,
11
+ MaxRequestBatchItems: config.MaxRequestBatchItems,
12
+ MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
13
+ MaxResponseBatchItems: responseBatchItems(config),
14
+ AuthToken: config.AuthToken,
15
+ }
16
+}
17
+
18
+func PosixServer(config TypedConfig) posix.ServerConfig {
19
+ return posix.ServerConfig{
20
+ SupportedProfiles: config.SupportedProfiles,
21
+ PreferredProfiles: config.PreferredProfiles,
22
+ MaxRequestBatchItems: config.MaxRequestBatchItems,
23
+ MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
24
+ MaxResponseBatchItems: responseBatchItems(config),
25
+ AuthToken: config.AuthToken,
26
+ }
27
+}
src/go/pkg/netipc/service/internal/transportconfig/types.go
new
+13
@@ -0,0 +1,13 @@
1
+package transportconfig
2
+
3
+type TypedConfig struct {
4
+ SupportedProfiles uint32
5
+ PreferredProfiles uint32
6
+ MaxRequestBatchItems uint32
7
+ MaxResponsePayloadBytes uint32
8
+ AuthToken uint64
9
+}
10
+
11
+func responseBatchItems(config TypedConfig) uint32 {
12
+ return config.MaxRequestBatchItems
13
+}
src/go/pkg/netipc/service/internal/transportconfig/windows.go
new
+27
@@ -0,0 +1,27 @@
1
+//go:build windows
2
+
3
+package transportconfig
4
+
5
+import "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
6
+
7
+func WindowsClient(config TypedConfig) windows.ClientConfig {
8
+ return windows.ClientConfig{
9
+ SupportedProfiles: config.SupportedProfiles,
10
+ PreferredProfiles: config.PreferredProfiles,
11
+ MaxRequestBatchItems: config.MaxRequestBatchItems,
12
+ MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
13
+ MaxResponseBatchItems: responseBatchItems(config),
14
+ AuthToken: config.AuthToken,
15
+ }
16
+}
17
+
18
+func WindowsServer(config TypedConfig) windows.ServerConfig {
19
+ return windows.ServerConfig{
20
+ SupportedProfiles: config.SupportedProfiles,
21
+ PreferredProfiles: config.PreferredProfiles,
22
+ MaxRequestBatchItems: config.MaxRequestBatchItems,
23
+ MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
24
+ MaxResponseBatchItems: responseBatchItems(config),
25
+ AuthToken: config.AuthToken,
26
+ }
27
+}
src/go/pkg/netipc/service/raw/apps_lookup.go
new
+109
@@ -0,0 +1,109 @@
1
+package raw
2
+
3
+import "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
4
+
5
+// AppsLookupHandler serves a single APPS_LOOKUP service kind.
6
+type AppsLookupHandler func(*protocol.AppsLookupRequestView, *protocol.AppsLookupBuilder) bool
7
+
8
+func appsLookupRequestSize(pids []uint32) (int, error) {
9
+ dirSize, err := checkedLookupMul(len(pids), protocol.LookupDirEntrySize)
10
+ if err != nil {
11
+ return 0, err
12
+ }
13
+ keySize, err := checkedLookupMul(len(pids), protocol.AppsLookupKeySize)
14
+ if err != nil {
15
+ return 0, err
16
+ }
17
+ size, err := checkedLookupAdd(protocol.AppsLookupReqHdr, dirSize)
18
+ if err != nil {
19
+ return 0, err
20
+ }
21
+ return checkedLookupAdd(size, keySize)
22
+}
23
+
24
+// CallAppsLookup performs a blocking typed APPS_LOOKUP call.
25
+// The returned view is valid until the next typed call on this client.
26
+func (c *Client) CallAppsLookup(pids []uint32) (*protocol.AppsLookupResponseView, error) {
27
+ if err := c.validateMethod(protocol.MethodAppsLookup); err != nil {
28
+ return nil, err
29
+ }
30
+
31
+ var result *protocol.AppsLookupResponseView
32
+ err := c.callWithRetry(func() error {
33
+ reqSize, err := appsLookupRequestSize(pids)
34
+ if err != nil {
35
+ return err
36
+ }
37
+ reqBuf := ensureClientScratch(&c.requestBuf, reqSize)
38
+ reqLen, err := protocol.EncodeAppsLookupRequest(pids, reqBuf)
39
+ if err != nil {
40
+ return err
41
+ }
42
+
43
+ _, payload, rerr := c.doRawCall(protocol.MethodAppsLookup, reqBuf[:reqLen])
44
+ if rerr != nil {
45
+ return rerr
46
+ }
47
+ view, derr := protocol.DecodeAppsLookupResponse(payload)
48
+ if derr != nil {
49
+ return derr
50
+ }
51
+ expectedCount, err := checkedLookupU32(len(pids))
52
+ if err != nil {
53
+ return err
54
+ }
55
+ if view.ItemCount != expectedCount {
56
+ return protocol.ErrBadItemCount
57
+ }
58
+ for i, expected := range pids {
59
+ itemIndex, ierr := checkedLookupU32(i)
60
+ if ierr != nil {
61
+ return ierr
62
+ }
63
+ item, ierr := view.Item(itemIndex)
64
+ if ierr != nil {
65
+ return ierr
66
+ }
67
+ if item.Pid != expected {
68
+ return protocol.ErrBadLayout
69
+ }
70
+ }
71
+ result = view
72
+ return nil
73
+ })
74
+ if err != nil {
75
+ return nil, err
76
+ }
77
+ return result, nil
78
+}
79
+
80
+// AppsLookupDispatch adapts a typed apps lookup handler to the raw dispatch shape.
81
+func AppsLookupDispatch(handle AppsLookupHandler) DispatchHandler {
82
+ if handle == nil {
83
+ return nil
84
+ }
85
+ return func(request []byte, responseBuf []byte) (int, error) {
86
+ req, err := protocol.DecodeAppsLookupRequest(request)
87
+ if err != nil {
88
+ return 0, err
89
+ }
90
+ minRequired, err := lookupMinRequired(protocol.AppsLookupRespHdr, req.ItemCount)
91
+ if err != nil {
92
+ return 0, err
93
+ }
94
+ if len(responseBuf) < minRequired {
95
+ return 0, protocol.ErrOverflow
96
+ }
97
+ builder := protocol.NewAppsLookupBuilder(responseBuf, req.ItemCount, 0)
98
+ if !handle(req, builder) {
99
+ return 0, errHandlerFailed
100
+ }
101
+ if builder.Error() != nil {
102
+ return 0, builder.Error()
103
+ }
104
+ if builder.ItemCount() != req.ItemCount {
105
+ return 0, protocol.ErrBadItemCount
106
+ }
107
+ return builder.Finish(), nil
108
+ }
109
+}
src/go/pkg/netipc/service/raw/apps_lookup_unix.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
+)
9
+
10
+// NewAppsLookupClient creates a raw client bound to the apps-lookup service kind.
11
+func NewAppsLookupClient(runDir, serviceName string, config posix.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodAppsLookup)
13
+}
src/go/pkg/netipc/service/raw/apps_lookup_windows.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build windows
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
8
+)
9
+
10
+// NewAppsLookupClient creates a raw client bound to the apps-lookup service kind.
11
+func NewAppsLookupClient(runDir, serviceName string, config windows.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodAppsLookup)
13
+}
src/go/pkg/netipc/service/raw/cache_bucket_test.go
new
+41
@@ -0,0 +1,41 @@
1
+package raw
2
+
3
+import (
4
+ "errors"
5
+ "testing"
6
+
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
8
+)
9
+
10
+func TestCacheBucketCountForItemCount(t *testing.T) {
11
+ tests := []struct {
12
+ name string
13
+ itemCount uint32
14
+ want uint32
15
+ wantErr bool
16
+ }{
17
+ {name: "empty", itemCount: 0, want: 0},
18
+ {name: "one", itemCount: 1, want: 16},
19
+ {name: "eight", itemCount: 8, want: 16},
20
+ {name: "nine", itemCount: 9, want: 32},
21
+ {name: "overflow", itemCount: (1 << 30) + 1, wantErr: true},
22
+ }
23
+
24
+ for _, tt := range tests {
25
+ t.Run(tt.name, func(t *testing.T) {
26
+ got, err := cacheBucketCountForItemCount(tt.itemCount)
27
+ if tt.wantErr {
28
+ if !errors.Is(err, protocol.ErrOverflow) {
29
+ t.Fatalf("expected ErrOverflow, got %v", err)
30
+ }
31
+ return
32
+ }
33
+ if err != nil {
34
+ t.Fatalf("unexpected error: %v", err)
35
+ }
36
+ if got != tt.want {
37
+ t.Fatalf("bucket count = %d, want %d", got, tt.want)
38
+ }
39
+ })
40
+ }
41
+}
src/go/pkg/netipc/service/raw/cache_windows.go
deleted
-159
@@ -1,159 +0,0 @@
1
-//go:build windows
2
-
3
-// L3: Client-side cgroups snapshot cache (Windows).
4
-//
5
-// Identical cache logic as the POSIX version. Uses Windows Client.
6
-//
7
-// Pure Go — no cgo. Works with CGO_ENABLED=0.
8
-
9
-package raw
10
-
11
-import (
12
- "time"
13
-
14
- windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
15
-)
16
-
17
-// cacheBucket is one open-addressing bucket for hash+name lookup.
18
-type cacheBucket struct {
19
- index int
20
- used bool
21
-}
22
-
23
-func cacheHashName(name string) uint32 {
24
- h := uint32(5381)
25
- for i := 0; i < len(name); i++ {
26
- h = ((h << 5) + h) + uint32(name[i])
27
- }
28
- return h
29
-}
30
-
31
-// Cache is an L3 client-side cgroups snapshot cache.
32
-type Cache struct {
33
- client *Client
34
- items []CacheItem
35
- // Open-addressing hash table: (hash ^ djb2(name)) -> index into items slice.
36
- buckets []cacheBucket
37
-
38
- systemdEnabled uint32
39
- generation uint64
40
- populated bool
41
- refreshSuccessCount uint32
42
- refreshFailureCount uint32
43
- epoch time.Time
44
- lastRefreshTs int64
45
-}
46
-
47
-// NewCache creates a new L3 cache.
48
-func NewCache(runDir, serviceName string, config windows.ClientConfig) *Cache {
49
- return &Cache{
50
- client: NewSnapshotClient(runDir, serviceName, config),
51
- epoch: time.Now(),
52
- }
53
-}
54
-
55
-// Refresh drives the L2 client and requests a fresh snapshot.
56
-func (c *Cache) Refresh() bool {
57
- c.client.Refresh()
58
-
59
- view, err := c.client.CallSnapshot()
60
- if err != nil {
61
- c.refreshFailureCount++
62
- return false
63
- }
64
-
65
- newItems := make([]CacheItem, 0, view.ItemCount)
66
- for i := uint32(0); i < view.ItemCount; i++ {
67
- iv, ierr := view.Item(i)
68
- if ierr != nil {
69
- c.refreshFailureCount++
70
- return false
71
- }
72
- newItems = append(newItems, CacheItem{
73
- Hash: iv.Hash,
74
- Options: iv.Options,
75
- Enabled: iv.Enabled,
76
- Name: iv.Name.String(),
77
- Path: iv.Path.String(),
78
- })
79
- }
80
-
81
- // Rebuild open-addressing lookup table.
82
- var buckets []cacheBucket
83
- if len(newItems) > 0 {
84
- bcount := nextPowerOf2U32(uint32(len(newItems)) * 2)
85
- buckets = make([]cacheBucket, bcount)
86
- mask := bcount - 1
87
- for i := range newItems {
88
- slot := (newItems[i].Hash ^ cacheHashName(newItems[i].Name)) & mask
89
- for buckets[slot].used {
90
- slot = (slot + 1) & mask
91
- }
92
- buckets[slot].index = i
93
- buckets[slot].used = true
94
- }
95
- }
96
-
97
- c.items = newItems
98
- c.buckets = buckets
99
- c.systemdEnabled = view.SystemdEnabled
100
- c.generation = view.Generation
101
- c.populated = true
102
- c.refreshSuccessCount++
103
- c.lastRefreshTs = time.Since(c.epoch).Milliseconds()
104
-
105
- return true
106
-}
107
-
108
-// Ready returns true if at least one successful refresh has occurred.
109
-func (c *Cache) Ready() bool {
110
- return c.populated
111
-}
112
-
113
-// Lookup finds a cached item by hash + name. O(1) via open-addressing hash
114
-// table. No I/O.
115
-func (c *Cache) Lookup(hash uint32, name string) (CacheItem, bool) {
116
- if !c.populated {
117
- return CacheItem{}, false
118
- }
119
- if len(c.buckets) > 0 {
120
- mask := uint32(len(c.buckets) - 1)
121
- slot := (hash ^ cacheHashName(name)) & mask
122
- for c.buckets[slot].used {
123
- item := c.items[c.buckets[slot].index]
124
- if item.Hash == hash && item.Name == name {
125
- return item, true
126
- }
127
- slot = (slot + 1) & mask
128
- }
129
- return CacheItem{}, false
130
- }
131
- for i := range c.items {
132
- if c.items[i].Hash == hash && c.items[i].Name == name {
133
- return c.items[i], true
134
- }
135
- }
136
- return CacheItem{}, false
137
-}
138
-
139
-// Status returns a diagnostic snapshot for the L3 cache.
140
-func (c *Cache) Status() CacheStatus {
141
- return CacheStatus{
142
- Populated: c.populated,
143
- ItemCount: uint32(len(c.items)),
144
- SystemdEnabled: c.systemdEnabled,
145
- Generation: c.generation,
146
- RefreshSuccessCount: c.refreshSuccessCount,
147
- RefreshFailureCount: c.refreshFailureCount,
148
- ConnectionState: c.client.state,
149
- LastRefreshTs: c.lastRefreshTs,
150
- }
151
-}
152
-
153
-// Close frees all cached items and closes the L2 client.
154
-func (c *Cache) Close() {
155
- c.items = nil
156
- c.buckets = nil
157
- c.populated = false
158
- c.client.Close()
159
-}
src/go/pkg/netipc/service/raw/cgroups_cache.go
renamed
+63
-19
@@ -1,15 +1,36 @@
1
-//go:build unix
2
-
3
-// L3: Client-side cgroups snapshot cache (POSIX).
4
-
1
package raw
2
3
import (
4
"time"
5
10
- "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
)
8
9
+// Default response buffer size for L3 cache refresh.
10
+const cacheResponseBufSize = 65536
11
+
12
+// CacheItem is an owned copy of a single cgroup item.
13
+// Built from ephemeral L2 views during cache construction.
14
+type CacheItem struct {
15
+ Hash uint32
16
+ Options uint32
17
+ Enabled uint32
18
+ Name string // owned copy
19
+ Path string // owned copy
20
+}
21
+
22
+// CacheStatus is a diagnostic snapshot for the L3 cache.
23
+type CacheStatus struct {
24
+ Populated bool
25
+ ItemCount uint32
26
+ SystemdEnabled uint32
27
+ Generation uint64
28
+ RefreshSuccessCount uint32
29
+ RefreshFailureCount uint32
30
+ ConnectionState ClientState // underlying L2 client state
31
+ LastRefreshTs int64 // monotonic timestamp (ms) of last successful refresh, 0 if never
32
+}
33
+
34
// cacheBucket is one open-addressing bucket for hash+name lookup.
35
type cacheBucket struct {
36
index int
@@ -40,21 +61,15 @@ type Cache struct {
61
lastRefreshTs int64 // elapsed ms since epoch
62
}
63
43
-// NewCache creates a new L3 cache. Creates the underlying L2 client
44
-// context. Does NOT connect. Does NOT require the server to be running.
45
-// Cache starts empty (populated == false).
46
-func NewCache(runDir, serviceName string, config posix.ClientConfig) *Cache {
64
+func newCache(client *Client) *Cache {
65
return &Cache{
48
- client: NewSnapshotClient(runDir, serviceName, config),
66
+ client: client,
67
epoch: time.Now(),
68
}
69
}
70
53
-// Refresh drives the L2 client (connect/reconnect as needed) and
54
-// requests a fresh snapshot. On success, rebuilds the local cache.
55
-// On failure, preserves the previous cache.
56
-//
57
-// Returns true if the cache was updated.
71
+// Refresh drives the L2 client and requests a fresh snapshot. On success,
72
+// it rebuilds the local cache. On failure, it preserves the previous cache.
73
func (c *Cache) Refresh() bool {
74
c.client.Refresh()
75
@@ -80,10 +95,18 @@ func (c *Cache) Refresh() bool {
95
})
96
}
97
83
- // Rebuild open-addressing lookup table.
98
var buckets []cacheBucket
99
if len(newItems) > 0 {
86
- bcount := nextPowerOf2U32(uint32(len(newItems)) * 2)
100
+ itemCount, err := checkedLookupU32(len(newItems))
101
+ if err != nil {
102
+ c.refreshFailureCount++
103
+ return false
104
+ }
105
+ bcount, err := cacheBucketCountForItemCount(itemCount)
106
+ if err != nil {
107
+ c.refreshFailureCount++
108
+ return false
109
+ }
110
buckets = make([]cacheBucket, bcount)
111
mask := bcount - 1
112
for i := range newItems {
@@ -107,6 +130,20 @@ func (c *Cache) Refresh() bool {
130
return true
131
}
132
133
+func cacheBucketCountForItemCount(itemCount uint32) (uint32, error) {
134
+ if itemCount == 0 {
135
+ return 0, nil
136
+ }
137
+ if itemCount > 1<<30 {
138
+ return 0, protocol.ErrOverflow
139
+ }
140
+ bcount := nextPowerOf2U32(itemCount * 2)
141
+ if bcount == 0 || uint64(bcount) > uint64(int(^uint(0)>>1)) {
142
+ return 0, protocol.ErrOverflow
143
+ }
144
+ return bcount, nil
145
+}
146
+
147
// Ready returns true if at least one successful refresh has occurred.
148
func (c *Cache) Ready() bool {
149
return c.populated
@@ -120,7 +157,10 @@ func (c *Cache) Lookup(hash uint32, name string) (CacheItem, bool) {
157
}
158
159
if len(c.buckets) > 0 {
123
- mask := uint32(len(c.buckets) - 1)
160
+ mask, err := checkedLookupU32(len(c.buckets) - 1)
161
+ if err != nil {
162
+ return CacheItem{}, false
163
+ }
164
slot := (hash ^ cacheHashName(name)) & mask
165
for c.buckets[slot].used {
166
item := &c.items[c.buckets[slot].index]
@@ -142,9 +182,13 @@ func (c *Cache) Lookup(hash uint32, name string) (CacheItem, bool) {
182
183
// Status returns a diagnostic snapshot for the L3 cache.
184
func (c *Cache) Status() CacheStatus {
185
+ itemCount, err := checkedLookupU32(len(c.items))
186
+ if err != nil {
187
+ itemCount = ^uint32(0)
188
+ }
189
return CacheStatus{
190
Populated: c.populated,
147
- ItemCount: uint32(len(c.items)),
191
+ ItemCount: itemCount,
192
SystemdEnabled: c.systemdEnabled,
193
Generation: c.generation,
194
RefreshSuccessCount: c.refreshSuccessCount,
src/go/pkg/netipc/service/raw/cgroups_cache_unix.go
new
+16
@@ -0,0 +1,16 @@
1
+//go:build unix
2
+
3
+// L3: Client-side cgroups snapshot cache (POSIX).
4
+
5
+package raw
6
+
7
+import (
8
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
9
+)
10
+
11
+// NewCache creates a new L3 cache. Creates the underlying L2 client
12
+// context. Does NOT connect. Does NOT require the server to be running.
13
+// Cache starts empty (populated == false).
14
+func NewCache(runDir, serviceName string, config posix.ClientConfig) *Cache {
15
+ return newCache(NewSnapshotClient(runDir, serviceName, config))
16
+}
src/go/pkg/netipc/service/raw/cgroups_cache_windows.go
new
+18
@@ -0,0 +1,18 @@
1
+//go:build windows
2
+
3
+// L3: Client-side cgroups snapshot cache (Windows).
4
+//
5
+// Identical cache logic as the POSIX version. Uses Windows Client.
6
+//
7
+// Pure Go — no cgo. Works with CGO_ENABLED=0.
8
+
9
+package raw
10
+
11
+import (
12
+ windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
13
+)
14
+
15
+// NewCache creates a new L3 cache.
16
+func NewCache(runDir, serviceName string, config windows.ClientConfig) *Cache {
17
+ return newCache(NewSnapshotClient(runDir, serviceName, config))
18
+}
src/go/pkg/netipc/service/raw/cgroups_lookup.go
new
+124
@@ -0,0 +1,124 @@
1
+package raw
2
+
3
+import (
4
+ "bytes"
5
+
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+)
8
+
9
+// CgroupsLookupHandler serves a single CGROUPS_LOOKUP service kind.
10
+type CgroupsLookupHandler func(*protocol.CgroupsLookupRequestView, *protocol.CgroupsLookupBuilder) bool
11
+
12
+func cgroupsLookupRequestSize(paths [][]byte) (int, error) {
13
+ dirSize, err := checkedLookupMul(len(paths), protocol.LookupDirEntrySize)
14
+ if err != nil {
15
+ return 0, err
16
+ }
17
+ size, err := checkedLookupAdd(protocol.CgroupsLookupReqHdr, dirSize)
18
+ if err != nil {
19
+ return 0, err
20
+ }
21
+ data := size
22
+ for _, path := range paths {
23
+ data, err = checkedLookupAlign8(data)
24
+ if err != nil {
25
+ return 0, err
26
+ }
27
+ data, err = checkedLookupAdd(data, len(path))
28
+ if err != nil {
29
+ return 0, err
30
+ }
31
+ data, err = checkedLookupAdd(data, 1)
32
+ if err != nil {
33
+ return 0, err
34
+ }
35
+ }
36
+ return data, nil
37
+}
38
+
39
+// CallCgroupsLookup performs a blocking typed CGROUPS_LOOKUP call.
40
+// The returned view is valid until the next typed call on this client.
41
+func (c *Client) CallCgroupsLookup(paths [][]byte) (*protocol.CgroupsLookupResponseView, error) {
42
+ if err := c.validateMethod(protocol.MethodCgroupsLookup); err != nil {
43
+ return nil, err
44
+ }
45
+
46
+ var result *protocol.CgroupsLookupResponseView
47
+ err := c.callWithRetry(func() error {
48
+ reqSize, err := cgroupsLookupRequestSize(paths)
49
+ if err != nil {
50
+ return err
51
+ }
52
+ reqBuf := ensureClientScratch(&c.requestBuf, reqSize)
53
+ reqLen, err := protocol.EncodeCgroupsLookupRequest(paths, reqBuf)
54
+ if err != nil {
55
+ return err
56
+ }
57
+
58
+ _, payload, rerr := c.doRawCall(protocol.MethodCgroupsLookup, reqBuf[:reqLen])
59
+ if rerr != nil {
60
+ return rerr
61
+ }
62
+ view, derr := protocol.DecodeCgroupsLookupResponse(payload)
63
+ if derr != nil {
64
+ return derr
65
+ }
66
+ expectedCount, err := checkedLookupU32(len(paths))
67
+ if err != nil {
68
+ return err
69
+ }
70
+ if view.ItemCount != expectedCount {
71
+ return protocol.ErrBadItemCount
72
+ }
73
+ for i, expected := range paths {
74
+ itemIndex, ierr := checkedLookupU32(i)
75
+ if ierr != nil {
76
+ return ierr
77
+ }
78
+ item, ierr := view.Item(itemIndex)
79
+ if ierr != nil {
80
+ return ierr
81
+ }
82
+ if !bytes.Equal(item.Path.Bytes(), expected) {
83
+ return protocol.ErrBadLayout
84
+ }
85
+ }
86
+ result = view
87
+ return nil
88
+ })
89
+ if err != nil {
90
+ return nil, err
91
+ }
92
+ return result, nil
93
+}
94
+
95
+// CgroupsLookupDispatch adapts a typed cgroups lookup handler to the raw dispatch shape.
96
+func CgroupsLookupDispatch(handle CgroupsLookupHandler) DispatchHandler {
97
+ if handle == nil {
98
+ return nil
99
+ }
100
+ return func(request []byte, responseBuf []byte) (int, error) {
101
+ req, err := protocol.DecodeCgroupsLookupRequest(request)
102
+ if err != nil {
103
+ return 0, err
104
+ }
105
+ minRequired, err := lookupMinRequired(protocol.CgroupsLookupRespHdr, req.ItemCount)
106
+ if err != nil {
107
+ return 0, err
108
+ }
109
+ if len(responseBuf) < minRequired {
110
+ return 0, protocol.ErrOverflow
111
+ }
112
+ builder := protocol.NewCgroupsLookupBuilder(responseBuf, req.ItemCount, 0)
113
+ if !handle(req, builder) {
114
+ return 0, errHandlerFailed
115
+ }
116
+ if builder.Error() != nil {
117
+ return 0, builder.Error()
118
+ }
119
+ if builder.ItemCount() != req.ItemCount {
120
+ return 0, protocol.ErrBadItemCount
121
+ }
122
+ return builder.Finish(), nil
123
+ }
124
+}
src/go/pkg/netipc/service/raw/cgroups_lookup_unix.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
+)
9
+
10
+// NewCgroupsLookupClient creates a raw client bound to the cgroups-lookup service kind.
11
+func NewCgroupsLookupClient(runDir, serviceName string, config posix.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodCgroupsLookup)
13
+}
src/go/pkg/netipc/service/raw/cgroups_lookup_windows.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build windows
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
8
+)
9
+
10
+// NewCgroupsLookupClient creates a raw client bound to the cgroups-lookup service kind.
11
+func NewCgroupsLookupClient(runDir, serviceName string, config windows.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodCgroupsLookup)
13
+}
src/go/pkg/netipc/service/raw/cgroups_snapshot.go
new
+78
@@ -0,0 +1,78 @@
1
+package raw
2
+
3
+import "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
4
+
5
+// SnapshotHandler serves a single CGROUPS_SNAPSHOT service kind.
6
+type SnapshotHandler func(*protocol.CgroupsRequest, *protocol.CgroupsBuilder) bool
7
+
8
+// SnapshotMaxItems returns the item budget for a single snapshot service kind.
9
+func SnapshotMaxItems(responseBufSize int, override uint32) uint32 {
10
+ if override != 0 {
11
+ return override
12
+ }
13
+ return protocol.EstimateCgroupsMaxItems(responseBufSize)
14
+}
15
+
16
+// CallSnapshot performs a blocking typed cgroups snapshot call.
17
+// The returned view is valid until the next typed call on this client.
18
+func (c *Client) CallSnapshot() (*protocol.CgroupsResponseView, error) {
19
+ if err := c.validateMethod(protocol.MethodCgroupsSnapshot); err != nil {
20
+ return nil, err
21
+ }
22
+
23
+ var result *protocol.CgroupsResponseView
24
+
25
+ err := c.callWithRetry(func() error {
26
+ req := protocol.CgroupsRequest{LayoutVersion: 1, Flags: 0}
27
+ var reqBuf [4]byte
28
+ if req.Encode(reqBuf[:]) == 0 {
29
+ return protocol.ErrTruncated
30
+ }
31
+
32
+ _, payload, rerr := c.doRawCall(protocol.MethodCgroupsSnapshot, reqBuf[:])
33
+ if rerr != nil {
34
+ return rerr
35
+ }
36
+
37
+ view, derr := protocol.DecodeCgroupsResponse(payload)
38
+ if derr != nil {
39
+ return derr
40
+ }
41
+ result = &view
42
+ return nil
43
+ })
44
+ if err != nil {
45
+ return nil, err
46
+ }
47
+ return result, nil
48
+}
49
+
50
+// SnapshotDispatch adapts a typed snapshot handler to the raw dispatch shape.
51
+func SnapshotDispatch(handle SnapshotHandler, maxItems uint32) DispatchHandler {
52
+ if handle == nil {
53
+ return nil
54
+ }
55
+ return func(request []byte, responseBuf []byte) (int, error) {
56
+ req, err := protocol.DecodeCgroupsRequest(request)
57
+ if err != nil {
58
+ return 0, err
59
+ }
60
+ itemBudget := SnapshotMaxItems(len(responseBuf), maxItems)
61
+ if itemBudget == 0 {
62
+ return 0, protocol.ErrOverflow
63
+ }
64
+ minRequired, ok := protocol.CgroupsBuilderMinBytes(itemBudget)
65
+ if !ok || len(responseBuf) < minRequired {
66
+ return 0, protocol.ErrOverflow
67
+ }
68
+ builder := protocol.NewCgroupsBuilder(responseBuf, itemBudget, 0, 0)
69
+ if !handle(&req, builder) {
70
+ return 0, errHandlerFailed
71
+ }
72
+ n := builder.Finish()
73
+ if n == 0 {
74
+ return 0, protocol.ErrOverflow
75
+ }
76
+ return n, nil
77
+ }
78
+}
src/go/pkg/netipc/service/raw/cgroups_snapshot_unix.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
+)
9
+
10
+// NewSnapshotClient creates a raw client bound to the cgroups-snapshot service kind.
11
+func NewSnapshotClient(runDir, serviceName string, config posix.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodCgroupsSnapshot)
13
+}
src/go/pkg/netipc/service/raw/cgroups_snapshot_windows.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build windows
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
8
+)
9
+
10
+// NewSnapshotClient creates a raw client bound to the cgroups-snapshot service kind.
11
+func NewSnapshotClient(runDir, serviceName string, config windows.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodCgroupsSnapshot)
13
+}
src/go/pkg/netipc/service/raw/client.go
-907
@@ -1,78 +1,15 @@
1
-//go:build unix
2
-
1
package raw
2
3
import (
6
- "encoding/binary"
4
"errors"
8
- "sync"
9
- "sync/atomic"
10
- "syscall"
5
"time"
12
- "unsafe"
6
7
"github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
15
- "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
)
9
10
const clientShmAttachRetryInterval = 5 * time.Millisecond
11
const clientShmAttachRetryTimeout = 5 * time.Second
12
21
-// ---------------------------------------------------------------------------
22
-// Client context
23
-// ---------------------------------------------------------------------------
24
-
25
-// Client is an internal L2 client context bound to one service kind.
26
-// It manages connection lifecycle and provides typed blocking calls with
27
-// at-least-once retry semantics.
28
-type Client struct {
29
- state ClientState
30
- runDir string
31
- serviceName string
32
- expectedMethodCode uint16
33
- config posix.ClientConfig
34
-
35
- // Connection (managed internally)
36
- session *posix.Session
37
- shm *posix.ShmContext
38
-
39
- // Reusable scratch buffers owned by the client for hot request paths.
40
- requestBuf []byte
41
- sendBuf []byte
42
- transportBuf []byte
43
-
44
- // Stats
45
- connectCount uint32
46
- reconnectCount uint32
47
- callCount uint32
48
- errorCount uint32
49
-}
50
-
51
-func newClient(runDir, serviceName string, config posix.ClientConfig, expectedMethodCode uint16) *Client {
52
- return &Client{
53
- state: StateDisconnected,
54
- runDir: runDir,
55
- serviceName: serviceName,
56
- expectedMethodCode: expectedMethodCode,
57
- config: config,
58
- }
59
-}
60
-
61
-// NewSnapshotClient creates a raw client bound to the cgroups-snapshot service kind.
62
-func NewSnapshotClient(runDir, serviceName string, config posix.ClientConfig) *Client {
63
- return newClient(runDir, serviceName, config, protocol.MethodCgroupsSnapshot)
64
-}
65
-
66
-// NewIncrementClient creates a raw client bound to the increment service kind.
67
-func NewIncrementClient(runDir, serviceName string, config posix.ClientConfig) *Client {
68
- return newClient(runDir, serviceName, config, protocol.MethodIncrement)
69
-}
70
-
71
-// NewStringReverseClient creates a raw client bound to the string-reverse service kind.
72
-func NewStringReverseClient(runDir, serviceName string, config posix.ClientConfig) *Client {
73
- return newClient(runDir, serviceName, config, protocol.MethodStringReverse)
74
-}
75
-
13
func nextPowerOf2U32(n uint32) uint32 {
14
if n < 16 {
15
return 16
@@ -292,852 +229,8 @@ func (c *Client) doRawCall(methodCode uint16, reqPayload []byte) (protocol.Heade
229
return respHdr, payload, nil
230
}
231
295
-// CallSnapshot performs a blocking typed cgroups snapshot call.
296
-// The returned view is valid until the next typed call on this client.
297
-func (c *Client) CallSnapshot() (*protocol.CgroupsResponseView, error) {
298
- if err := c.validateMethod(protocol.MethodCgroupsSnapshot); err != nil {
299
- return nil, err
300
- }
301
-
302
- var result *protocol.CgroupsResponseView
303
-
304
- err := c.callWithRetry(func() error {
305
- req := protocol.CgroupsRequest{LayoutVersion: 1, Flags: 0}
306
- var reqBuf [4]byte
307
- if req.Encode(reqBuf[:]) == 0 {
308
- return protocol.ErrTruncated
309
- }
310
-
311
- _, payload, rerr := c.doRawCall(protocol.MethodCgroupsSnapshot, reqBuf[:])
312
- if rerr != nil {
313
- return rerr
314
- }
315
-
316
- view, derr := protocol.DecodeCgroupsResponse(payload)
317
- if derr != nil {
318
- return derr
319
- }
320
- result = &view
321
- return nil
322
- })
323
- if err != nil {
324
- return nil, err
325
- }
326
- return result, nil
327
-}
328
-
329
-// CallIncrement performs a blocking INCREMENT call.
330
-// Sends requestValue, returns the server's response value.
331
-func (c *Client) CallIncrement(requestValue uint64) (uint64, error) {
332
- if err := c.validateMethod(protocol.MethodIncrement); err != nil {
333
- return 0, err
334
- }
335
-
336
- var result uint64
337
-
338
- err := c.callWithRetry(func() error {
339
- var reqBuf [protocol.IncrementPayloadSize]byte
340
- if protocol.IncrementEncode(requestValue, reqBuf[:]) == 0 {
341
- return protocol.ErrTruncated
342
- }
343
-
344
- _, payload, rerr := c.doRawCall(protocol.MethodIncrement, reqBuf[:])
345
- if rerr != nil {
346
- return rerr
347
- }
348
-
349
- val, derr := protocol.IncrementDecode(payload)
350
- if derr != nil {
351
- return derr
352
- }
353
- result = val
354
- return nil
355
- })
356
- return result, err
357
-}
358
-
359
-// CallStringReverse performs a blocking STRING_REVERSE call.
360
-// The returned view is valid until the next typed call on this client.
361
-func (c *Client) CallStringReverse(requestStr string) (*protocol.StringReverseView, error) {
362
- if err := c.validateMethod(protocol.MethodStringReverse); err != nil {
363
- return nil, err
364
- }
365
-
366
- var result *protocol.StringReverseView
367
-
368
- err := c.callWithRetry(func() error {
369
- reqBuf := ensureClientScratch(&c.requestBuf, protocol.StringReverseHdrSize+len(requestStr)+1)
370
- if protocol.StringReverseEncode(requestStr, reqBuf) == 0 {
371
- return protocol.ErrTruncated
372
- }
373
-
374
- _, payload, rerr := c.doRawCall(protocol.MethodStringReverse, reqBuf)
375
- if rerr != nil {
376
- return rerr
377
- }
378
-
379
- view, derr := protocol.StringReverseDecode(payload)
380
- if derr != nil {
381
- return derr
382
- }
383
- result = &view
384
- return nil
385
- })
386
- if err != nil {
387
- return nil, err
388
- }
389
- return result, nil
390
-}
391
-
392
-// CallIncrementBatch performs a blocking batch INCREMENT call.
393
-// Sends multiple values, returns the server's response values.
394
-func (c *Client) CallIncrementBatch(values []uint64) ([]uint64, error) {
395
- if err := c.validateMethod(protocol.MethodIncrement); err != nil {
396
- return nil, err
397
- }
398
-
399
- if len(values) == 0 {
400
- return nil, nil
401
- }
402
-
403
- var results []uint64
404
- itemCount := uint32(len(values))
405
-
406
- err := c.callWithRetry(func() error {
407
- // Build batch request payload
408
- batchBufSize := protocol.Align8(int(itemCount)*8) + int(itemCount)*protocol.IncrementPayloadSize + int(itemCount)*protocol.Alignment
409
- batchBuf := ensureClientScratch(&c.requestBuf, batchBufSize)
410
- bb := protocol.NewBatchBuilder(batchBuf, itemCount)
411
-
412
- for _, v := range values {
413
- var item [protocol.IncrementPayloadSize]byte
414
- if protocol.IncrementEncode(v, item[:]) == 0 {
415
- return protocol.ErrTruncated
416
- }
417
- if err := bb.Add(item[:]); err != nil {
418
- return err
419
- }
420
- }
421
-
422
- totalPayloadLen, _ := bb.Finish()
423
- reqPayload := batchBuf[:totalPayloadLen]
424
-
425
- // Build and send header with batch flags
426
- hdr := protocol.Header{
427
- Kind: protocol.KindRequest,
428
- Code: protocol.MethodIncrement,
429
- Flags: protocol.FlagBatch,
430
- ItemCount: itemCount,
431
- MessageID: uint64(c.callCount) + 1,
432
- TransportStatus: protocol.StatusOK,
433
- }
434
-
435
- if err := c.transportSend(&hdr, reqPayload); err != nil {
436
- return err
437
- }
438
-
439
- // Receive response
440
- respHdr, respPayload, err := c.transportReceive()
441
- if err != nil {
442
- return err
443
- }
444
-
445
- if respHdr.Kind != protocol.KindResponse {
446
- return protocol.ErrBadKind
447
- }
448
- if respHdr.Code != protocol.MethodIncrement {
449
- return protocol.ErrBadLayout
450
- }
451
- if respHdr.MessageID != hdr.MessageID {
452
- return protocol.ErrBadLayout
453
- }
454
- switch respHdr.TransportStatus {
455
- case protocol.StatusOK:
456
- case protocol.StatusLimitExceeded:
457
- if current := c.sessionMaxResponsePayloadBytes(); current > 0 {
458
- if current >= ^uint32(0)/2 {
459
- c.noteResponseCapacity(^uint32(0))
460
- } else {
461
- c.noteResponseCapacity(current * 2)
462
- }
463
- }
464
- return protocol.ErrOverflow
465
- default:
466
- return protocol.ErrBadLayout
467
- }
468
- if respHdr.Flags&protocol.FlagBatch == 0 || respHdr.ItemCount != itemCount {
469
- return protocol.ErrBadItemCount
470
- }
471
-
472
- // Extract each response item
473
- out := make([]uint64, itemCount)
474
- for i := range itemCount {
475
- itemData, gerr := protocol.BatchItemGet(respPayload, itemCount, i)
476
- if gerr != nil {
477
- return gerr
478
- }
479
- val, derr := protocol.IncrementDecode(itemData)
480
- if derr != nil {
481
- return derr
482
- }
483
- out[i] = val
484
- }
485
- results = out
486
- return nil
487
- })
488
- return results, err
489
-}
490
-
232
// Close tears down the connection and releases resources.
233
func (c *Client) Close() {
234
c.disconnect()
235
c.state = StateDisconnected
236
}
496
-
497
-// ------------------------------------------------------------------
498
-// Internal helpers
499
-// ------------------------------------------------------------------
500
-
501
-func (c *Client) disconnect() {
502
- if c.shm != nil {
503
- c.shm.ShmClose()
504
- c.shm = nil
505
- }
506
- if c.session != nil {
507
- c.session.Close()
508
- c.session = nil
509
- }
510
-}
511
-
512
-func (c *Client) tryConnect() ClientState {
513
- session, err := posix.Connect(c.runDir, c.serviceName, &c.config)
514
- if err != nil {
515
- switch {
516
- case isConnectError(err):
517
- return StateNotFound
518
- case isAuthError(err):
519
- return StateAuthFailed
520
- case isIncompatibleError(err):
521
- return StateIncompatible
522
- default:
523
- return StateDisconnected
524
- }
525
- }
526
-
527
- // SHM upgrade if negotiated
528
- if session.SelectedProfile == protocol.ProfileSHMHybrid ||
529
- session.SelectedProfile == protocol.ProfileSHMFutex {
530
- // Retry attach: the server prepared SHM before handshake, but the
531
- // client may still race slightly with the peer exposing the region.
532
- deadline := time.Now().Add(clientShmAttachRetryTimeout)
533
- for {
534
- shm, serr := posix.ShmClientAttach(c.runDir, c.serviceName, session.SessionID)
535
- if serr == nil {
536
- c.shm = shm
537
- break
538
- }
539
- if !time.Now().Before(deadline) {
540
- break
541
- }
542
- time.Sleep(clientShmAttachRetryInterval)
543
- }
544
- if c.shm == nil {
545
- // SHM attach failed after negotiation. Close that session,
546
- // blacklist SHM for this client context, and retry baseline.
547
- session.Close()
548
- c.config.SupportedProfiles &^= posixShmProfiles
549
- c.config.PreferredProfiles &^= posixShmProfiles
550
- if c.config.SupportedProfiles == 0 {
551
- return StateDisconnected
552
- }
553
- return c.tryConnect()
554
- }
555
- }
556
-
557
- c.session = session
558
- return StateReady
559
-}
560
-
561
-func (c *Client) transportSend(hdr *protocol.Header, payload []byte) error {
562
- if c.shm != nil {
563
- if len(payload) > int(c.sessionMaxRequestPayloadBytes()) {
564
- c.noteRequestCapacity(uint32(len(payload)))
565
- return protocol.ErrOverflow
566
- }
567
-
568
- msgLen := protocol.HeaderSize + len(payload)
569
- msg := ensureClientScratch(&c.sendBuf, msgLen)
570
-
571
- hdr.Magic = protocol.MagicMsg
572
- hdr.Version = protocol.Version
573
- hdr.HeaderLen = protocol.HeaderLen
574
- hdr.PayloadLen = uint32(len(payload))
575
-
576
- hdr.Encode(msg[:protocol.HeaderSize])
577
- if len(payload) > 0 {
578
- copy(msg[protocol.HeaderSize:], payload)
579
- }
580
-
581
- if err := c.shm.ShmSend(msg[:msgLen]); err != nil {
582
- if errors.Is(err, posix.ErrShmMsgTooLarge) {
583
- c.noteRequestCapacity(uint32(len(payload)))
584
- return protocol.ErrOverflow
585
- }
586
- return protocol.ErrTruncated
587
- }
588
- return nil
589
- }
590
-
591
- // UDS path
592
- if c.session == nil {
593
- return protocol.ErrTruncated
594
- }
595
- if err := c.session.Send(hdr, payload); err != nil {
596
- if errors.Is(err, posix.ErrLimitExceeded) {
597
- c.noteRequestCapacity(uint32(len(payload)))
598
- return protocol.ErrOverflow
599
- }
600
- return protocol.ErrTruncated
601
- }
602
- return nil
603
-}
604
-
605
-func (c *Client) transportReceive() (protocol.Header, []byte, error) {
606
- scratch := ensureClientScratch(&c.transportBuf, c.maxReceiveMessageBytes())
607
-
608
- if c.shm != nil {
609
- mlen, err := c.shm.ShmReceive(scratch, 30000)
610
- if err != nil {
611
- return protocol.Header{}, nil, protocol.ErrTruncated
612
- }
613
- if mlen < protocol.HeaderSize {
614
- return protocol.Header{}, nil, protocol.ErrTruncated
615
- }
616
-
617
- hdr, err := protocol.DecodeHeader(scratch[:mlen])
618
- if err != nil {
619
- return protocol.Header{}, nil, err
620
- }
621
- return hdr, scratch[protocol.HeaderSize:mlen], nil
622
- }
623
-
624
- // UDS path: receive returns (Header, payload, error)
625
- if c.session == nil {
626
- return protocol.Header{}, nil, protocol.ErrTruncated
627
- }
628
-
629
- hdr, payload, err := c.session.Receive(scratch)
630
- if err != nil {
631
- return protocol.Header{}, nil, protocol.ErrTruncated
632
- }
633
- return hdr, payload, nil
634
-}
635
-
636
-func (c *Client) maxReceiveMessageBytes() int {
637
- maxPayload := c.config.MaxResponsePayloadBytes
638
- if c.session != nil && c.session.MaxResponsePayloadBytes > 0 {
639
- maxPayload = c.session.MaxResponsePayloadBytes
640
- }
641
- if maxPayload == 0 {
642
- maxPayload = cacheResponseBufSize
643
- }
644
- return protocol.HeaderSize + int(maxPayload)
645
-}
646
-
647
-// Error classification helpers
648
-func isConnectError(err error) bool {
649
- return errors.Is(err, posix.ErrConnect) || errors.Is(err, posix.ErrSocket)
650
-}
651
-
652
-func isAuthError(err error) bool {
653
- return errors.Is(err, posix.ErrAuthFailed)
654
-}
655
-
656
-func isIncompatibleError(err error) bool {
657
- return errors.Is(err, posix.ErrNoProfile) || errors.Is(err, posix.ErrIncompatible)
658
-}
659
-
660
-// ---------------------------------------------------------------------------
661
-// Managed server
662
-// ---------------------------------------------------------------------------
663
-
664
-// Server is an internal managed server bound to one expected request kind.
665
-// Supports multiple concurrent client sessions up to workerCount.
666
-type Server struct {
667
- runDir string
668
- serviceName string
669
- config posix.ServerConfig
670
- expectedMethodCode uint16
671
- handler DispatchHandler
672
- running atomic.Bool
673
- learnedRequestPayloadBytes atomic.Uint32
674
- learnedResponsePayloadBytes atomic.Uint32
675
- nextSessionID atomic.Uint64
676
- workerCount int
677
- wg sync.WaitGroup
678
-}
679
-
680
-// NewServer creates a new managed server. workerCount limits the
681
-// maximum number of concurrent client sessions (default 1 if <= 0).
682
-func NewServer(
683
- runDir, serviceName string,
684
- config posix.ServerConfig,
685
- expectedMethodCode uint16,
686
- handler DispatchHandler,
687
-) *Server {
688
- return NewServerWithWorkers(runDir, serviceName, config, expectedMethodCode, handler, 8)
689
-}
690
-
691
-// NewServerWithWorkers creates a server with an explicit worker count limit.
692
-func NewServerWithWorkers(
693
- runDir, serviceName string,
694
- config posix.ServerConfig,
695
- expectedMethodCode uint16,
696
- handler DispatchHandler,
697
- workerCount int,
698
-) *Server {
699
- if workerCount < 1 {
700
- workerCount = 1
701
- }
702
- learnedRequest := config.MaxRequestPayloadBytes
703
- if learnedRequest == 0 {
704
- learnedRequest = protocol.MaxPayloadDefault
705
- }
706
- learnedResponse := config.MaxResponsePayloadBytes
707
- if learnedResponse == 0 {
708
- learnedResponse = protocol.MaxPayloadDefault
709
- }
710
- s := &Server{
711
- runDir: runDir,
712
- serviceName: serviceName,
713
- config: config,
714
- expectedMethodCode: expectedMethodCode,
715
- handler: handler,
716
- workerCount: workerCount,
717
- }
718
- s.learnedRequestPayloadBytes.Store(learnedRequest)
719
- s.learnedResponsePayloadBytes.Store(learnedResponse)
720
- // Session ids are 1-based; prepareAcceptConfig() allocates with Add(1).
721
- s.nextSessionID.Store(0)
722
- return s
723
-}
724
-
725
-func (s *Server) dispatchSingle(methodCode uint16, request []byte, responseBuf []byte) (int, error) {
726
- if methodCode != s.expectedMethodCode || s.handler == nil {
727
- return 0, errHandlerFailed
728
- }
729
-
730
- return s.handler(request, responseBuf)
731
-}
732
-
733
-func (s *Server) methodSupported(methodCode uint16) bool {
734
- return s.handler != nil && methodCode == s.expectedMethodCode
735
-}
736
-
737
-func serverNotePayloadCapacity(target *atomic.Uint32, payloadLen uint32) {
738
- grown := nextPowerOf2U32(payloadLen)
739
- for {
740
- current := target.Load()
741
- if grown <= current {
742
- return
743
- }
744
- if target.CompareAndSwap(current, grown) {
745
- return
746
- }
747
- }
748
-}
749
-
750
-const posixShmProfiles = protocol.ProfileSHMHybrid | protocol.ProfileSHMFutex
751
-
752
-func (s *Server) prepareAcceptConfig() (uint64, posix.ServerConfig, *posix.ShmContext, bool) {
753
- sessionID := s.nextSessionID.Add(1)
754
- cfg := s.config
755
- cfg.MaxRequestPayloadBytes = s.learnedRequestPayloadBytes.Load()
756
- cfg.MaxResponsePayloadBytes = s.learnedResponsePayloadBytes.Load()
757
-
758
- if cfg.SupportedProfiles&posixShmProfiles == 0 {
759
- return sessionID, cfg, nil, true
760
- }
761
-
762
- shm, err := posix.ShmServerCreate(
763
- s.runDir, s.serviceName, sessionID,
764
- cfg.MaxRequestPayloadBytes+uint32(protocol.HeaderSize),
765
- cfg.MaxResponsePayloadBytes+uint32(protocol.HeaderSize),
766
- )
767
- if err == nil {
768
- return sessionID, cfg, shm, true
769
- }
770
-
771
- cfg.SupportedProfiles &^= posixShmProfiles
772
- cfg.PreferredProfiles &^= posixShmProfiles
773
- if cfg.SupportedProfiles == 0 {
774
- return sessionID, cfg, nil, false
775
- }
776
-
777
- return sessionID, cfg, nil, true
778
-}
779
-
780
-// Run starts the acceptor loop. Blocking. Accepts clients, spawns a
781
-// goroutine per session (up to workerCount concurrently).
782
-// Returns when Stop() is called or on fatal error.
783
-func (s *Server) Run() error {
784
- posix.ShmCleanupStale(s.runDir, s.serviceName)
785
-
786
- listener, err := posix.Listen(s.runDir, s.serviceName, s.config)
787
- if err != nil {
788
- return err
789
- }
790
- defer listener.Close()
791
-
792
- s.running.Store(true)
793
-
794
- /* Semaphore channel limits concurrent sessions */
795
- sem := make(chan struct{}, s.workerCount)
796
-
797
- for s.running.Load() {
798
- // Poll the listener fd before blocking on accept
799
- ready := pollFd(listener.Fd(), serverPollTimeoutMs)
800
- if ready < 0 {
801
- break
802
- }
803
- if ready == 0 {
804
- continue
805
- }
806
-
807
- sessionID, acceptCfg, precreatedShm, ok := s.prepareAcceptConfig()
808
- if !ok {
809
- time.Sleep(10 * time.Millisecond)
810
- continue
811
- }
812
-
813
- session, err := listener.AcceptWithConfig(sessionID, acceptCfg)
814
- if err != nil {
815
- if precreatedShm != nil {
816
- precreatedShm.ShmDestroy()
817
- }
818
- if !s.running.Load() {
819
- break
820
- }
821
- time.Sleep(10 * time.Millisecond)
822
- continue
823
- }
824
-
825
- // Try to acquire a worker slot (non-blocking check)
826
- select {
827
- case sem <- struct{}{}:
828
- // Got a slot
829
- default:
830
- // At capacity: reject client
831
- if precreatedShm != nil {
832
- precreatedShm.ShmDestroy()
833
- }
834
- session.Close()
835
- continue
836
- }
837
-
838
- var shm *posix.ShmContext
839
- if session.SelectedProfile == protocol.ProfileSHMHybrid ||
840
- session.SelectedProfile == protocol.ProfileSHMFutex {
841
- if precreatedShm == nil {
842
- session.Close()
843
- <-sem
844
- continue
845
- }
846
- shm = precreatedShm
847
- } else if precreatedShm != nil {
848
- precreatedShm.ShmDestroy()
849
- }
850
-
851
- // Handle this session in a goroutine
852
- s.wg.Add(1)
853
- go func(sess *posix.Session, shmCtx *posix.ShmContext) {
854
- defer func() {
855
- if r := recover(); r != nil {
856
- // Session handler panicked; log but don't crash the server
857
- }
858
- <-sem // release worker slot
859
- s.wg.Done()
860
- }()
861
- s.handleSession(sess, shmCtx)
862
- }(session, shm)
863
- }
864
-
865
- // Wait for all active session goroutines to finish
866
- s.wg.Wait()
867
-
868
- return nil
869
-}
870
-
871
-// Stop signals the server to stop.
872
-func (s *Server) Stop() {
873
- s.running.Store(false)
874
-}
875
-
876
-func (s *Server) handleSession(session *posix.Session, shm *posix.ShmContext) {
877
- recvBuf := make([]byte, protocol.HeaderSize+int(session.MaxRequestPayloadBytes))
878
- respBuf := make([]byte, int(session.MaxResponsePayloadBytes))
879
- itemRespBuf := make([]byte, int(session.MaxResponsePayloadBytes))
880
- msgBuf := make([]byte, int(session.MaxResponsePayloadBytes)+protocol.HeaderSize)
881
-
882
- defer func() {
883
- if shm != nil {
884
- shm.ShmDestroy()
885
- }
886
- session.Close()
887
- }()
888
-
889
- for s.running.Load() {
890
- var hdr protocol.Header
891
- var payload []byte
892
-
893
- if shm != nil {
894
- mlen, err := shm.ShmReceive(recvBuf, serverPollTimeoutMs)
895
- if err != nil {
896
- if err == posix.ErrShmTimeout {
897
- continue
898
- }
899
- return
900
- }
901
- if mlen < protocol.HeaderSize {
902
- return
903
- }
904
- h, err := protocol.DecodeHeader(recvBuf[:mlen])
905
- if err != nil {
906
- return
907
- }
908
- hdr = h
909
- payload = recvBuf[protocol.HeaderSize:mlen]
910
- } else {
911
- // Poll the session fd before blocking on receive
912
- ready := pollFd(session.Fd(), serverPollTimeoutMs)
913
- if ready < 0 {
914
- return
915
- }
916
- if ready == 0 {
917
- continue
918
- }
919
-
920
- h, p, err := session.Receive(recvBuf)
921
- if err != nil {
922
- return
923
- }
924
- hdr = h
925
- payload = p
926
- }
927
-
928
- // Protocol violation: unexpected message kind terminates session
929
- if hdr.Kind != protocol.KindRequest {
930
- return
931
- }
932
-
933
- if len(payload) <= int(^uint32(0)) {
934
- serverNotePayloadCapacity(&s.learnedRequestPayloadBytes, uint32(len(payload)))
935
- }
936
-
937
- if !s.methodSupported(hdr.Code) {
938
- respHdr := protocol.Header{
939
- Kind: protocol.KindResponse,
940
- Code: hdr.Code,
941
- MessageID: hdr.MessageID,
942
- TransportStatus: protocol.StatusUnsupported,
943
- ItemCount: 1,
944
- }
945
-
946
- if shm != nil {
947
- if len(msgBuf) < protocol.HeaderSize {
948
- msgBuf = make([]byte, protocol.HeaderSize)
949
- }
950
- msg := msgBuf[:protocol.HeaderSize]
951
- respHdr.Magic = protocol.MagicMsg
952
- respHdr.Version = protocol.Version
953
- respHdr.HeaderLen = protocol.HeaderLen
954
- respHdr.PayloadLen = 0
955
- respHdr.Encode(msg[:protocol.HeaderSize])
956
- if err := shm.ShmSend(msg); err != nil {
957
- return
958
- }
959
- } else if err := session.Send(&respHdr, nil); err != nil {
960
- return
961
- }
962
- continue
963
- }
964
-
965
- // Dispatch: single-item or batch
966
- responseLen := 0
967
- isBatch := (hdr.Flags&protocol.FlagBatch != 0) && hdr.ItemCount >= 1
968
- var dispatchErr error
969
-
970
- if !isBatch {
971
- var derr error
972
- responseLen, derr = s.dispatchSingle(hdr.Code, payload, respBuf)
973
- if derr != nil {
974
- dispatchErr = derr
975
- responseLen = 0
976
- } else if responseLen < 0 || responseLen > len(respBuf) {
977
- dispatchErr = protocol.ErrOverflow
978
- responseLen = 0
979
- }
980
- } else {
981
- var bb protocol.BatchBuilder
982
- bb.Reset(respBuf, hdr.ItemCount)
983
-
984
- for i := uint32(0); i < hdr.ItemCount && dispatchErr == nil; i++ {
985
- itemData, gerr := protocol.BatchItemGet(payload, hdr.ItemCount, i)
986
- if gerr != nil {
987
- dispatchErr = gerr
988
- break
989
- }
990
-
991
- itemResultLen, derr := s.dispatchSingle(hdr.Code, itemData, itemRespBuf)
992
- if derr != nil {
993
- dispatchErr = derr
994
- break
995
- }
996
- if itemResultLen < 0 || itemResultLen > len(itemRespBuf) {
997
- dispatchErr = protocol.ErrOverflow
998
- break
999
- }
1000
-
1001
- if aerr := bb.Add(itemRespBuf[:itemResultLen]); aerr != nil {
1002
- dispatchErr = aerr
1003
- break
1004
- }
1005
- }
1006
-
1007
- if dispatchErr == nil {
1008
- responseLen, _ = bb.Finish()
1009
- }
1010
- }
1011
-
1012
- // Build response header
1013
- respHdr := protocol.Header{
1014
- Kind: protocol.KindResponse,
1015
- Code: hdr.Code,
1016
- MessageID: hdr.MessageID,
1017
- }
1018
-
1019
- if dispatchErr == nil {
1020
- if responseLen <= int(^uint32(0)) {
1021
- serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, uint32(responseLen))
1022
- }
1023
- respHdr.TransportStatus = protocol.StatusOK
1024
- if isBatch {
1025
- respHdr.Flags = protocol.FlagBatch
1026
- respHdr.ItemCount = hdr.ItemCount
1027
- } else {
1028
- respHdr.ItemCount = 1
1029
- }
1030
- } else if errors.Is(dispatchErr, protocol.ErrOverflow) {
1031
- current := session.MaxResponsePayloadBytes
1032
- if current >= ^uint32(0)/2 {
1033
- serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, ^uint32(0))
1034
- } else {
1035
- serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, current*2)
1036
- }
1037
- respHdr.TransportStatus = protocol.StatusLimitExceeded
1038
- respHdr.ItemCount = 1
1039
- responseLen = 0
1040
- } else if errors.Is(dispatchErr, errHandlerFailed) {
1041
- respHdr.TransportStatus = protocol.StatusInternalError
1042
- respHdr.ItemCount = 1
1043
- responseLen = 0
1044
- } else {
1045
- respHdr.TransportStatus = protocol.StatusBadEnvelope
1046
- respHdr.ItemCount = 1
1047
- responseLen = 0
1048
- }
1049
-
1050
- // Send response via the active transport
1051
- if shm != nil {
1052
- msgLen := protocol.HeaderSize + responseLen
1053
- if len(msgBuf) < msgLen {
1054
- msgBuf = make([]byte, msgLen)
1055
- }
1056
- msg := msgBuf[:msgLen]
1057
-
1058
- respHdr.Magic = protocol.MagicMsg
1059
- respHdr.Version = protocol.Version
1060
- respHdr.HeaderLen = protocol.HeaderLen
1061
- respHdr.PayloadLen = uint32(responseLen)
1062
-
1063
- respHdr.Encode(msg[:protocol.HeaderSize])
1064
- if responseLen > 0 {
1065
- copy(msg[protocol.HeaderSize:], respBuf[:responseLen])
1066
- }
1067
-
1068
- if err := shm.ShmSend(msg); err != nil {
1069
- return
1070
- }
1071
- if respHdr.TransportStatus == protocol.StatusLimitExceeded {
1072
- return
1073
- }
1074
- } else {
1075
- if err := session.Send(&respHdr, respBuf[:responseLen]); err != nil {
1076
- return
1077
- }
1078
- if respHdr.TransportStatus == protocol.StatusLimitExceeded {
1079
- return
1080
- }
1081
- }
1082
- }
1083
-}
1084
-
1085
-// ---------------------------------------------------------------------------
1086
-// Internal: poll helper (raw syscall, pure Go, no cgo)
1087
-// ---------------------------------------------------------------------------
1088
-
1089
-// poll constants (not exported by Go's syscall package)
1090
-const (
1091
- _POLLIN = 0x0001
1092
- _POLLERR = 0x0008
1093
- _POLLHUP = 0x0010
1094
- _POLLNVAL = 0x0020
1095
-)
1096
-
1097
-// pollfd matches struct pollfd from <poll.h>.
1098
-type pollfd struct {
1099
- fd int32
1100
- events int16
1101
- revents int16
1102
-}
1103
-
1104
-// pollFd polls a file descriptor for readability with a timeout in ms.
1105
-// Returns: 1 = data ready, 0 = timeout, -1 = error/hangup.
1106
-func pollFd(fd int, timeoutMs int) int {
1107
- pfd := pollfd{
1108
- fd: int32(fd),
1109
- events: _POLLIN,
1110
- }
1111
-
1112
- r, _, errno := syscall.Syscall(
1113
- syscall.SYS_POLL,
1114
- uintptr(unsafe.Pointer(&pfd)),
1115
- 1,
1116
- uintptr(timeoutMs),
1117
- )
1118
-
1119
- n := int(r)
1120
- if n < 0 {
1121
- if errno == syscall.EINTR {
1122
- return 0
1123
- }
1124
- return -1
1125
- }
1126
-
1127
- if n == 0 {
1128
- return 0
1129
- }
1130
-
1131
- if pfd.revents&(_POLLERR|_POLLHUP|_POLLNVAL) != 0 {
1132
- return -1
1133
- }
1134
-
1135
- if pfd.revents&_POLLIN != 0 {
1136
- return 1
1137
- }
1138
-
1139
- return 0
1140
-}
1141
-
1142
-// Suppress unused import warnings.
1143
-var _ = binary.NativeEndian
src/go/pkg/netipc/service/raw/client_unix.go
new
+215
@@ -0,0 +1,215 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "errors"
7
+ "time"
8
+
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
10
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
11
+)
12
+
13
+// Client is an internal L2 client context bound to one service kind.
14
+// It manages connection lifecycle and provides typed blocking calls with
15
+// at-least-once retry semantics.
16
+type Client struct {
17
+ state ClientState
18
+ runDir string
19
+ serviceName string
20
+ expectedMethodCode uint16
21
+ config posix.ClientConfig
22
+
23
+ // Connection (managed internally)
24
+ session *posix.Session
25
+ shm *posix.ShmContext
26
+
27
+ // Reusable scratch buffers owned by the client for hot request paths.
28
+ requestBuf []byte
29
+ sendBuf []byte
30
+ transportBuf []byte
31
+
32
+ // Stats
33
+ connectCount uint32
34
+ reconnectCount uint32
35
+ callCount uint32
36
+ errorCount uint32
37
+}
38
+
39
+func newClient(runDir, serviceName string, config posix.ClientConfig, expectedMethodCode uint16) *Client {
40
+ return &Client{
41
+ state: StateDisconnected,
42
+ runDir: runDir,
43
+ serviceName: serviceName,
44
+ expectedMethodCode: expectedMethodCode,
45
+ config: config,
46
+ }
47
+}
48
+
49
+func (c *Client) disconnect() {
50
+ if c.shm != nil {
51
+ c.shm.ShmClose()
52
+ c.shm = nil
53
+ }
54
+ if c.session != nil {
55
+ c.session.Close()
56
+ c.session = nil
57
+ }
58
+}
59
+
60
+func (c *Client) tryConnect() ClientState {
61
+ session, err := posix.Connect(c.runDir, c.serviceName, &c.config)
62
+ if err != nil {
63
+ switch {
64
+ case isConnectError(err):
65
+ return StateNotFound
66
+ case isAuthError(err):
67
+ return StateAuthFailed
68
+ case isIncompatibleError(err):
69
+ return StateIncompatible
70
+ default:
71
+ return StateDisconnected
72
+ }
73
+ }
74
+
75
+ // SHM upgrade if negotiated
76
+ if session.SelectedProfile == protocol.ProfileSHMHybrid ||
77
+ session.SelectedProfile == protocol.ProfileSHMFutex {
78
+ // Retry attach: the server prepared SHM before handshake, but the
79
+ // client may still race slightly with the peer exposing the region.
80
+ deadline := time.Now().Add(clientShmAttachRetryTimeout)
81
+ for {
82
+ shm, serr := posix.ShmClientAttach(c.runDir, c.serviceName, session.SessionID)
83
+ if serr == nil {
84
+ c.shm = shm
85
+ break
86
+ }
87
+ if !time.Now().Before(deadline) {
88
+ break
89
+ }
90
+ time.Sleep(clientShmAttachRetryInterval)
91
+ }
92
+ if c.shm == nil {
93
+ // SHM attach failed after negotiation. Close that session,
94
+ // blacklist SHM for this client context, and retry baseline.
95
+ session.Close()
96
+ c.config.SupportedProfiles &^= posixShmProfiles
97
+ c.config.PreferredProfiles &^= posixShmProfiles
98
+ if c.config.SupportedProfiles == 0 {
99
+ return StateDisconnected
100
+ }
101
+ return c.tryConnect()
102
+ }
103
+ }
104
+
105
+ c.session = session
106
+ return StateReady
107
+}
108
+
109
+func (c *Client) transportSend(hdr *protocol.Header, payload []byte) error {
110
+ payloadLen, err := checkedLookupU32(len(payload))
111
+ if err != nil {
112
+ c.noteRequestCapacity(^uint32(0))
113
+ return protocol.ErrOverflow
114
+ }
115
+
116
+ if c.shm != nil {
117
+ if payloadLen > c.sessionMaxRequestPayloadBytes() {
118
+ c.noteRequestCapacity(payloadLen)
119
+ return protocol.ErrOverflow
120
+ }
121
+
122
+ msgLen, err := checkedLookupAdd(protocol.HeaderSize, len(payload))
123
+ if err != nil {
124
+ return protocol.ErrOverflow
125
+ }
126
+ msg := ensureClientScratch(&c.sendBuf, msgLen)
127
+
128
+ hdr.Magic = protocol.MagicMsg
129
+ hdr.Version = protocol.Version
130
+ hdr.HeaderLen = protocol.HeaderLen
131
+ hdr.PayloadLen = payloadLen
132
+
133
+ hdr.Encode(msg[:protocol.HeaderSize])
134
+ if len(payload) > 0 {
135
+ copy(msg[protocol.HeaderSize:], payload)
136
+ }
137
+
138
+ if err := c.shm.ShmSend(msg[:msgLen]); err != nil {
139
+ if errors.Is(err, posix.ErrShmMsgTooLarge) {
140
+ c.noteRequestCapacity(payloadLen)
141
+ return protocol.ErrOverflow
142
+ }
143
+ return protocol.ErrTruncated
144
+ }
145
+ return nil
146
+ }
147
+
148
+ // UDS path
149
+ if c.session == nil {
150
+ return protocol.ErrTruncated
151
+ }
152
+ if err := c.session.Send(hdr, payload); err != nil {
153
+ if errors.Is(err, posix.ErrLimitExceeded) {
154
+ c.noteRequestCapacity(payloadLen)
155
+ return protocol.ErrOverflow
156
+ }
157
+ return protocol.ErrTruncated
158
+ }
159
+ return nil
160
+}
161
+
162
+func (c *Client) transportReceive() (protocol.Header, []byte, error) {
163
+ scratch := ensureClientScratch(&c.transportBuf, c.maxReceiveMessageBytes())
164
+
165
+ if c.shm != nil {
166
+ mlen, err := c.shm.ShmReceive(scratch, 30000)
167
+ if err != nil {
168
+ return protocol.Header{}, nil, protocol.ErrTruncated
169
+ }
170
+ if mlen < protocol.HeaderSize {
171
+ return protocol.Header{}, nil, protocol.ErrTruncated
172
+ }
173
+
174
+ hdr, err := protocol.DecodeHeader(scratch[:mlen])
175
+ if err != nil {
176
+ return protocol.Header{}, nil, err
177
+ }
178
+ return hdr, scratch[protocol.HeaderSize:mlen], nil
179
+ }
180
+
181
+ // UDS path: receive returns (Header, payload, error)
182
+ if c.session == nil {
183
+ return protocol.Header{}, nil, protocol.ErrTruncated
184
+ }
185
+
186
+ hdr, payload, err := c.session.Receive(scratch)
187
+ if err != nil {
188
+ return protocol.Header{}, nil, protocol.ErrTruncated
189
+ }
190
+ return hdr, payload, nil
191
+}
192
+
193
+func (c *Client) maxReceiveMessageBytes() int {
194
+ maxPayload := c.config.MaxResponsePayloadBytes
195
+ if c.session != nil && c.session.MaxResponsePayloadBytes > 0 {
196
+ maxPayload = c.session.MaxResponsePayloadBytes
197
+ }
198
+ if maxPayload == 0 {
199
+ maxPayload = cacheResponseBufSize
200
+ }
201
+ return protocol.HeaderSize + int(maxPayload)
202
+}
203
+
204
+// Error classification helpers
205
+func isConnectError(err error) bool {
206
+ return errors.Is(err, posix.ErrConnect) || errors.Is(err, posix.ErrSocket)
207
+}
208
+
209
+func isAuthError(err error) bool {
210
+ return errors.Is(err, posix.ErrAuthFailed)
211
+}
212
+
213
+func isIncompatibleError(err error) bool {
214
+ return errors.Is(err, posix.ErrNoProfile) || errors.Is(err, posix.ErrIncompatible)
215
+}
src/go/pkg/netipc/service/raw/client_windows.go
-919
@@ -1,32 +1,15 @@
1
//go:build windows
2
3
-// Internal typed service client for Windows.
4
-//
5
-// Identical state machine and retry logic as the POSIX client.
6
-// Uses Named Pipe + Win SHM transports instead of UDS + POSIX SHM.
7
-//
8
-// Pure Go — no cgo. Works with CGO_ENABLED=0.
9
-
3
package raw
4
5
import (
13
- "encoding/binary"
6
"errors"
15
- "sync"
16
- "sync/atomic"
7
"time"
8
9
"github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
10
windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
11
)
12
23
-const clientShmAttachRetryInterval = 5 * time.Millisecond
24
-const clientShmAttachRetryTimeout = 5 * time.Second
25
-
26
-// ---------------------------------------------------------------------------
27
-// Client context
28
-// ---------------------------------------------------------------------------
29
-
13
// Client is an internal L2 client context bound to one service kind.
14
type Client struct {
15
state ClientState
@@ -58,437 +41,6 @@ func newClient(runDir, serviceName string, config windows.ClientConfig, expected
41
}
42
}
43
61
-// NewSnapshotClient creates a raw client bound to the cgroups-snapshot service kind.
62
-func NewSnapshotClient(runDir, serviceName string, config windows.ClientConfig) *Client {
63
- return newClient(runDir, serviceName, config, protocol.MethodCgroupsSnapshot)
64
-}
65
-
66
-// NewIncrementClient creates a raw client bound to the increment service kind.
67
-func NewIncrementClient(runDir, serviceName string, config windows.ClientConfig) *Client {
68
- return newClient(runDir, serviceName, config, protocol.MethodIncrement)
69
-}
70
-
71
-// NewStringReverseClient creates a raw client bound to the string-reverse service kind.
72
-func NewStringReverseClient(runDir, serviceName string, config windows.ClientConfig) *Client {
73
- return newClient(runDir, serviceName, config, protocol.MethodStringReverse)
74
-}
75
-
76
-func nextPowerOf2U32(n uint32) uint32 {
77
- if n < 16 {
78
- return 16
79
- }
80
- n--
81
- n |= n >> 1
82
- n |= n >> 2
83
- n |= n >> 4
84
- n |= n >> 8
85
- n |= n >> 16
86
- return n + 1
87
-}
88
-
89
-func (c *Client) validateMethod(methodCode uint16) error {
90
- if c.expectedMethodCode != methodCode {
91
- return protocol.ErrBadLayout
92
- }
93
- return nil
94
-}
95
-
96
-// Refresh attempts connect if DISCONNECTED/NOT_FOUND, reconnect if BROKEN.
97
-func (c *Client) Refresh() bool {
98
- oldState := c.state
99
-
100
- switch c.state {
101
- case StateDisconnected, StateNotFound:
102
- c.state = StateConnecting
103
- c.state = c.tryConnect()
104
- if c.state == StateReady {
105
- c.connectCount++
106
- }
107
-
108
- case StateBroken:
109
- c.disconnect()
110
- c.state = StateConnecting
111
- c.state = c.tryConnect()
112
- if c.state == StateReady {
113
- c.reconnectCount++
114
- }
115
-
116
- case StateReady, StateConnecting, StateAuthFailed, StateIncompatible:
117
- // No action needed
118
- }
119
-
120
- return c.state != oldState
121
-}
122
-
123
-// Ready returns true only if the client is in the READY state.
124
-func (c *Client) Ready() bool {
125
- return c.state == StateReady
126
-}
127
-
128
-// Status returns a diagnostic counters snapshot.
129
-func (c *Client) Status() ClientStatus {
130
- return ClientStatus{
131
- State: c.state,
132
- ConnectCount: c.connectCount,
133
- ReconnectCount: c.reconnectCount,
134
- CallCount: c.callCount,
135
- ErrorCount: c.errorCount,
136
- }
137
-}
138
-
139
-func (c *Client) sessionMaxRequestPayloadBytes() uint32 {
140
- if c.session != nil {
141
- return c.session.MaxRequestPayloadBytes
142
- }
143
- return c.config.MaxRequestPayloadBytes
144
-}
145
-
146
-func (c *Client) sessionMaxResponsePayloadBytes() uint32 {
147
- if c.session != nil {
148
- return c.session.MaxResponsePayloadBytes
149
- }
150
- return c.config.MaxResponsePayloadBytes
151
-}
152
-
153
-func (c *Client) noteRequestCapacity(payloadLen uint32) {
154
- grown := nextPowerOf2U32(payloadLen)
155
- if grown > protocol.MaxPayloadCap {
156
- grown = protocol.MaxPayloadCap
157
- }
158
- if grown > c.config.MaxRequestPayloadBytes {
159
- c.config.MaxRequestPayloadBytes = grown
160
- }
161
-}
162
-
163
-func (c *Client) noteResponseCapacity(payloadLen uint32) {
164
- grown := nextPowerOf2U32(payloadLen)
165
- if grown > protocol.MaxPayloadCap {
166
- grown = protocol.MaxPayloadCap
167
- }
168
- if grown > c.config.MaxResponsePayloadBytes {
169
- c.config.MaxResponsePayloadBytes = grown
170
- }
171
-}
172
-
173
-// callWithRetry manages reconnect-driven recovery for a typed call.
174
-// Ordinary failures retry once. Overflow-driven resize recovery may
175
-// reconnect more than once while negotiated capacities grow.
176
-func (c *Client) callWithRetry(attempt func() error) error {
177
- if c.state != StateReady {
178
- c.errorCount++
179
- return protocol.ErrBadLayout
180
- }
181
-
182
- for {
183
- prevReq := c.sessionMaxRequestPayloadBytes()
184
- prevResp := c.sessionMaxResponsePayloadBytes()
185
- prevCfgReq := c.config.MaxRequestPayloadBytes
186
- prevCfgResp := c.config.MaxResponsePayloadBytes
187
-
188
- firstErr := attempt()
189
- if firstErr == nil {
190
- c.callCount++
191
- return nil
192
- }
193
-
194
- if !errors.Is(firstErr, protocol.ErrOverflow) {
195
- c.disconnect()
196
- c.state = StateBroken
197
-
198
- c.state = c.tryConnect()
199
- if c.state != StateReady {
200
- c.errorCount++
201
- return firstErr
202
- }
203
- c.reconnectCount++
204
-
205
- retryErr := attempt()
206
- if retryErr == nil {
207
- c.callCount++
208
- return nil
209
- }
210
-
211
- c.disconnect()
212
- c.state = StateBroken
213
- c.errorCount++
214
- return retryErr
215
- }
216
-
217
- c.disconnect()
218
- c.state = StateBroken
219
-
220
- c.state = c.tryConnect()
221
- if c.state != StateReady {
222
- c.errorCount++
223
- return firstErr
224
- }
225
- c.reconnectCount++
226
-
227
- if c.sessionMaxRequestPayloadBytes() <= prevReq &&
228
- c.sessionMaxResponsePayloadBytes() <= prevResp &&
229
- c.config.MaxRequestPayloadBytes <= prevCfgReq &&
230
- c.config.MaxResponsePayloadBytes <= prevCfgResp {
231
- c.disconnect()
232
- c.state = StateBroken
233
- c.errorCount++
234
- return firstErr
235
- }
236
- }
237
-}
238
-
239
-// doRawCall sends a request and receives/validates the response envelope.
240
-// Returns the validated response header and a borrowed payload view.
241
-func (c *Client) doRawCall(methodCode uint16, reqPayload []byte) (protocol.Header, []byte, error) {
242
- hdr := protocol.Header{
243
- Kind: protocol.KindRequest,
244
- Code: methodCode,
245
- Flags: 0,
246
- ItemCount: 1,
247
- MessageID: uint64(c.callCount) + 1,
248
- TransportStatus: protocol.StatusOK,
249
- }
250
-
251
- if err := c.transportSend(&hdr, reqPayload); err != nil {
252
- return protocol.Header{}, nil, err
253
- }
254
-
255
- respHdr, payload, err := c.transportReceive()
256
- if err != nil {
257
- return protocol.Header{}, nil, err
258
- }
259
-
260
- if respHdr.Kind != protocol.KindResponse {
261
- return protocol.Header{}, nil, protocol.ErrBadKind
262
- }
263
- if respHdr.Code != methodCode {
264
- return protocol.Header{}, nil, protocol.ErrBadLayout
265
- }
266
- if respHdr.MessageID != hdr.MessageID {
267
- return protocol.Header{}, nil, protocol.ErrBadLayout
268
- }
269
- switch respHdr.TransportStatus {
270
- case protocol.StatusOK:
271
- case protocol.StatusLimitExceeded:
272
- if current := c.sessionMaxResponsePayloadBytes(); current > 0 {
273
- if current >= ^uint32(0)/2 {
274
- c.noteResponseCapacity(^uint32(0))
275
- } else {
276
- c.noteResponseCapacity(current * 2)
277
- }
278
- }
279
- return protocol.Header{}, nil, protocol.ErrOverflow
280
- default:
281
- return protocol.Header{}, nil, protocol.ErrBadLayout
282
- }
283
-
284
- return respHdr, payload, nil
285
-}
286
-
287
-// CallSnapshot performs a blocking typed cgroups snapshot call.
288
-func (c *Client) CallSnapshot() (*protocol.CgroupsResponseView, error) {
289
- if err := c.validateMethod(protocol.MethodCgroupsSnapshot); err != nil {
290
- return nil, err
291
- }
292
-
293
- var result *protocol.CgroupsResponseView
294
-
295
- err := c.callWithRetry(func() error {
296
- req := protocol.CgroupsRequest{LayoutVersion: 1, Flags: 0}
297
- var reqBuf [4]byte
298
- if req.Encode(reqBuf[:]) == 0 {
299
- return protocol.ErrTruncated
300
- }
301
-
302
- _, payload, rerr := c.doRawCall(protocol.MethodCgroupsSnapshot, reqBuf[:])
303
- if rerr != nil {
304
- return rerr
305
- }
306
-
307
- view, derr := protocol.DecodeCgroupsResponse(payload)
308
- if derr != nil {
309
- return derr
310
- }
311
- result = &view
312
- return nil
313
- })
314
- if err != nil {
315
- return nil, err
316
- }
317
- return result, nil
318
-}
319
-
320
-// CallIncrement performs a blocking INCREMENT call.
321
-// Sends requestValue, returns the server's response value.
322
-func (c *Client) CallIncrement(requestValue uint64) (uint64, error) {
323
- if err := c.validateMethod(protocol.MethodIncrement); err != nil {
324
- return 0, err
325
- }
326
-
327
- var result uint64
328
-
329
- err := c.callWithRetry(func() error {
330
- var reqBuf [protocol.IncrementPayloadSize]byte
331
- if protocol.IncrementEncode(requestValue, reqBuf[:]) == 0 {
332
- return protocol.ErrTruncated
333
- }
334
-
335
- _, payload, rerr := c.doRawCall(protocol.MethodIncrement, reqBuf[:])
336
- if rerr != nil {
337
- return rerr
338
- }
339
-
340
- val, derr := protocol.IncrementDecode(payload)
341
- if derr != nil {
342
- return derr
343
- }
344
- result = val
345
- return nil
346
- })
347
- return result, err
348
-}
349
-
350
-// CallStringReverse performs a blocking STRING_REVERSE call.
351
-// Sends requestStr, returns the server's reversed string view.
352
-func (c *Client) CallStringReverse(requestStr string) (*protocol.StringReverseView, error) {
353
- if err := c.validateMethod(protocol.MethodStringReverse); err != nil {
354
- return nil, err
355
- }
356
-
357
- var result *protocol.StringReverseView
358
-
359
- err := c.callWithRetry(func() error {
360
- reqBuf := ensureClientScratch(&c.requestBuf, protocol.StringReverseHdrSize+len(requestStr)+1)
361
- if protocol.StringReverseEncode(requestStr, reqBuf) == 0 {
362
- return protocol.ErrTruncated
363
- }
364
-
365
- _, payload, rerr := c.doRawCall(protocol.MethodStringReverse, reqBuf)
366
- if rerr != nil {
367
- return rerr
368
- }
369
-
370
- view, derr := protocol.StringReverseDecode(payload)
371
- if derr != nil {
372
- return derr
373
- }
374
- result = &view
375
- return nil
376
- })
377
- if err != nil {
378
- return nil, err
379
- }
380
- return result, nil
381
-}
382
-
383
-// CallIncrementBatch performs a blocking batch INCREMENT call.
384
-// Sends multiple values, returns the server's response values.
385
-func (c *Client) CallIncrementBatch(values []uint64) ([]uint64, error) {
386
- if err := c.validateMethod(protocol.MethodIncrement); err != nil {
387
- return nil, err
388
- }
389
-
390
- if len(values) == 0 {
391
- return nil, nil
392
- }
393
-
394
- var results []uint64
395
- itemCount := uint32(len(values))
396
-
397
- err := c.callWithRetry(func() error {
398
- // Build batch request payload
399
- batchBufSize := protocol.Align8(int(itemCount)*8) + int(itemCount)*protocol.IncrementPayloadSize + int(itemCount)*protocol.Alignment
400
- batchBuf := ensureClientScratch(&c.requestBuf, batchBufSize)
401
- bb := protocol.NewBatchBuilder(batchBuf, itemCount)
402
-
403
- for _, v := range values {
404
- var item [protocol.IncrementPayloadSize]byte
405
- if protocol.IncrementEncode(v, item[:]) == 0 {
406
- return protocol.ErrTruncated
407
- }
408
- if err := bb.Add(item[:]); err != nil {
409
- return err
410
- }
411
- }
412
-
413
- totalPayloadLen, _ := bb.Finish()
414
- reqPayload := batchBuf[:totalPayloadLen]
415
-
416
- // Build and send header with batch flags
417
- hdr := protocol.Header{
418
- Kind: protocol.KindRequest,
419
- Code: protocol.MethodIncrement,
420
- Flags: protocol.FlagBatch,
421
- ItemCount: itemCount,
422
- MessageID: uint64(c.callCount) + 1,
423
- TransportStatus: protocol.StatusOK,
424
- }
425
-
426
- if err := c.transportSend(&hdr, reqPayload); err != nil {
427
- return err
428
- }
429
-
430
- // Receive response
431
- respHdr, respPayload, err := c.transportReceive()
432
- if err != nil {
433
- return err
434
- }
435
-
436
- if respHdr.Kind != protocol.KindResponse {
437
- return protocol.ErrBadKind
438
- }
439
- if respHdr.Code != protocol.MethodIncrement {
440
- return protocol.ErrBadLayout
441
- }
442
- if respHdr.MessageID != hdr.MessageID {
443
- return protocol.ErrBadLayout
444
- }
445
- switch respHdr.TransportStatus {
446
- case protocol.StatusOK:
447
- case protocol.StatusLimitExceeded:
448
- if current := c.sessionMaxResponsePayloadBytes(); current > 0 {
449
- if current >= ^uint32(0)/2 {
450
- c.noteResponseCapacity(^uint32(0))
451
- } else {
452
- c.noteResponseCapacity(current * 2)
453
- }
454
- }
455
- return protocol.ErrOverflow
456
- default:
457
- return protocol.ErrBadLayout
458
- }
459
- if respHdr.Flags&protocol.FlagBatch == 0 || respHdr.ItemCount != itemCount {
460
- return protocol.ErrBadItemCount
461
- }
462
-
463
- // Extract each response item
464
- out := make([]uint64, itemCount)
465
- for i := uint32(0); i < itemCount; i++ {
466
- itemData, gerr := protocol.BatchItemGet(respPayload, itemCount, i)
467
- if gerr != nil {
468
- return gerr
469
- }
470
- val, derr := protocol.IncrementDecode(itemData)
471
- if derr != nil {
472
- return derr
473
- }
474
- out[i] = val
475
- }
476
- results = out
477
- return nil
478
- })
479
- return results, err
480
-}
481
-
482
-// Close tears down the connection and releases resources.
483
-func (c *Client) Close() {
484
- c.disconnect()
485
- c.state = StateDisconnected
486
-}
487
-
488
-// ------------------------------------------------------------------
489
-// Internal helpers
490
-// ------------------------------------------------------------------
491
-
44
func (c *Client) disconnect() {
45
if c.shm != nil {
46
c.shm.WinShmClose()
@@ -648,474 +200,3 @@ func isAuthError(err error) bool {
200
func isIncompatibleError(err error) bool {
201
return errors.Is(err, windows.ErrNoProfile) || errors.Is(err, windows.ErrIncompatible)
202
}
651
-
652
-// ---------------------------------------------------------------------------
653
-// Managed server
654
-// ---------------------------------------------------------------------------
655
-
656
-// Server is an internal managed server bound to one expected request kind.
657
-type Server struct {
658
- runDir string
659
- serviceName string
660
- config windows.ServerConfig
661
- expectedMethodCode uint16
662
- handler DispatchHandler
663
- running atomic.Bool
664
- learnedRequestPayloadBytes atomic.Uint32
665
- learnedResponsePayloadBytes atomic.Uint32
666
- nextSessionID atomic.Uint64
667
- workerCount int
668
- wg sync.WaitGroup
669
- listener *windows.Listener // stored so Stop() can close it
670
-}
671
-
672
-// NewServer creates a new managed server.
673
-func NewServer(
674
- runDir, serviceName string,
675
- config windows.ServerConfig,
676
- expectedMethodCode uint16,
677
- handler DispatchHandler,
678
-) *Server {
679
- return NewServerWithWorkers(runDir, serviceName, config, expectedMethodCode, handler, 8)
680
-}
681
-
682
-// NewServerWithWorkers creates a server with an explicit worker count limit.
683
-func NewServerWithWorkers(
684
- runDir, serviceName string,
685
- config windows.ServerConfig,
686
- expectedMethodCode uint16,
687
- handler DispatchHandler,
688
- workerCount int,
689
-) *Server {
690
- if workerCount < 1 {
691
- workerCount = 1
692
- }
693
- learnedRequest := config.MaxRequestPayloadBytes
694
- if learnedRequest == 0 {
695
- learnedRequest = protocol.MaxPayloadDefault
696
- }
697
- learnedResponse := config.MaxResponsePayloadBytes
698
- if learnedResponse == 0 {
699
- learnedResponse = protocol.MaxPayloadDefault
700
- }
701
- s := &Server{
702
- runDir: runDir,
703
- serviceName: serviceName,
704
- config: config,
705
- expectedMethodCode: expectedMethodCode,
706
- handler: handler,
707
- workerCount: workerCount,
708
- }
709
- s.learnedRequestPayloadBytes.Store(learnedRequest)
710
- s.learnedResponsePayloadBytes.Store(learnedResponse)
711
- // Session ids are 1-based; prepareAcceptConfig() allocates with Add(1).
712
- s.nextSessionID.Store(0)
713
- return s
714
-}
715
-
716
-func (s *Server) dispatchSingle(methodCode uint16, request []byte, responseBuf []byte) (int, error) {
717
- if methodCode != s.expectedMethodCode || s.handler == nil {
718
- return 0, errHandlerFailed
719
- }
720
-
721
- return s.handler(request, responseBuf)
722
-}
723
-
724
-func (s *Server) methodSupported(methodCode uint16) bool {
725
- return s.handler != nil && methodCode == s.expectedMethodCode
726
-}
727
-
728
-func serverNotePayloadCapacity(target *atomic.Uint32, payloadLen uint32) {
729
- grown := nextPowerOf2U32(payloadLen)
730
- for {
731
- current := target.Load()
732
- if grown <= current {
733
- return
734
- }
735
- if target.CompareAndSwap(current, grown) {
736
- return
737
- }
738
- }
739
-}
740
-
741
-type preparedWinShm struct {
742
- hybrid *windows.WinShmContext
743
- busywait *windows.WinShmContext
744
-}
745
-
746
-func (p *preparedWinShm) take(profile uint32) *windows.WinShmContext {
747
- if p == nil {
748
- return nil
749
- }
750
- switch profile {
751
- case windows.WinShmProfileHybrid:
752
- ctx := p.hybrid
753
- p.hybrid = nil
754
- return ctx
755
- case windows.WinShmProfileBusywait:
756
- ctx := p.busywait
757
- p.busywait = nil
758
- return ctx
759
- default:
760
- return nil
761
- }
762
-}
763
-
764
-func (p *preparedWinShm) destroyAll() {
765
- if p == nil {
766
- return
767
- }
768
- if p.hybrid != nil {
769
- p.hybrid.WinShmDestroy()
770
- p.hybrid = nil
771
- }
772
- if p.busywait != nil {
773
- p.busywait.WinShmDestroy()
774
- p.busywait = nil
775
- }
776
-}
777
-
778
-const winShmProfiles = windows.WinShmProfileHybrid | windows.WinShmProfileBusywait
779
-
780
-func (s *Server) prepareAcceptConfig() (uint64, windows.ServerConfig, *preparedWinShm, bool) {
781
- sessionID := s.nextSessionID.Add(1)
782
- cfg := s.config
783
- cfg.MaxRequestPayloadBytes = s.learnedRequestPayloadBytes.Load()
784
- cfg.MaxResponsePayloadBytes = s.learnedResponsePayloadBytes.Load()
785
-
786
- if cfg.SupportedProfiles&winShmProfiles == 0 {
787
- return sessionID, cfg, nil, true
788
- }
789
-
790
- prepared := &preparedWinShm{}
791
- for _, profile := range []uint32{windows.WinShmProfileHybrid, windows.WinShmProfileBusywait} {
792
- if cfg.SupportedProfiles&profile == 0 {
793
- continue
794
- }
795
- shm, err := windows.WinShmServerCreate(
796
- s.runDir, s.serviceName,
797
- cfg.AuthToken,
798
- sessionID,
799
- profile,
800
- cfg.MaxRequestPayloadBytes+uint32(protocol.HeaderSize),
801
- cfg.MaxResponsePayloadBytes+uint32(protocol.HeaderSize),
802
- )
803
- if err != nil {
804
- cfg.SupportedProfiles &^= profile
805
- cfg.PreferredProfiles &^= profile
806
- continue
807
- }
808
- if profile == windows.WinShmProfileHybrid {
809
- prepared.hybrid = shm
810
- } else {
811
- prepared.busywait = shm
812
- }
813
- }
814
-
815
- if cfg.SupportedProfiles == 0 {
816
- prepared.destroyAll()
817
- return sessionID, cfg, nil, false
818
- }
819
-
820
- if prepared.hybrid == nil && prepared.busywait == nil {
821
- return sessionID, cfg, nil, true
822
- }
823
-
824
- return sessionID, cfg, prepared, true
825
-}
826
-
827
-// Run starts the acceptor loop. Blocking.
828
-func (s *Server) Run() error {
829
- listener, err := windows.Listen(s.runDir, s.serviceName, s.config)
830
- if err != nil {
831
- return err
832
- }
833
- s.listener = listener
834
- defer func() {
835
- listener.Close()
836
- s.listener = nil
837
- }()
838
-
839
- s.running.Store(true)
840
- sem := make(chan struct{}, s.workerCount)
841
-
842
- for s.running.Load() {
843
- sessionID, acceptCfg, preparedShm, ok := s.prepareAcceptConfig()
844
- if !ok {
845
- time.Sleep(10 * time.Millisecond)
846
- continue
847
- }
848
-
849
- session, err := listener.AcceptWithConfig(sessionID, acceptCfg)
850
- if err != nil {
851
- if preparedShm != nil {
852
- preparedShm.destroyAll()
853
- }
854
- if !s.running.Load() {
855
- break
856
- }
857
- time.Sleep(10 * time.Millisecond)
858
- continue
859
- }
860
-
861
- select {
862
- case sem <- struct{}{}:
863
- default:
864
- if preparedShm != nil {
865
- preparedShm.destroyAll()
866
- }
867
- session.Close()
868
- continue
869
- }
870
-
871
- var shm *windows.WinShmContext
872
- if session.SelectedProfile == windows.WinShmProfileHybrid ||
873
- session.SelectedProfile == windows.WinShmProfileBusywait {
874
- shm = preparedShm.take(session.SelectedProfile)
875
- if shm == nil {
876
- if preparedShm != nil {
877
- preparedShm.destroyAll()
878
- }
879
- session.Close()
880
- <-sem
881
- continue
882
- }
883
- }
884
- if preparedShm != nil {
885
- preparedShm.destroyAll()
886
- }
887
-
888
- s.wg.Add(1)
889
- go func(sess *windows.Session, shmCtx *windows.WinShmContext) {
890
- defer func() {
891
- if r := recover(); r != nil {
892
- // Session handler panicked; log but don't crash the server
893
- }
894
- <-sem
895
- s.wg.Done()
896
- }()
897
- s.handleSession(sess, shmCtx)
898
- }(session, shm)
899
- }
900
-
901
- s.wg.Wait()
902
- return nil
903
-}
904
-
905
-// Stop signals the server to stop and unblocks Accept by closing the listener.
906
-func (s *Server) Stop() {
907
- s.running.Store(false)
908
- if s.listener != nil {
909
- s.listener.Close()
910
- }
911
-}
912
-
913
-func (s *Server) handleSession(session *windows.Session, shm *windows.WinShmContext) {
914
- recvBuf := make([]byte, protocol.HeaderSize+int(session.MaxRequestPayloadBytes))
915
- respBuf := make([]byte, int(session.MaxResponsePayloadBytes))
916
- itemRespBuf := make([]byte, int(session.MaxResponsePayloadBytes))
917
- msgBuf := make([]byte, int(session.MaxResponsePayloadBytes)+protocol.HeaderSize)
918
-
919
- defer func() {
920
- if shm != nil {
921
- shm.WinShmDestroy()
922
- }
923
- session.Close()
924
- }()
925
-
926
- for s.running.Load() {
927
- var hdr protocol.Header
928
- var payload []byte
929
-
930
- if shm != nil {
931
- mlen, err := shm.WinShmReceive(recvBuf, serverPollTimeoutMs)
932
- if err != nil {
933
- if err == windows.ErrWinShmTimeout {
934
- continue
935
- }
936
- return
937
- }
938
- if mlen < protocol.HeaderSize {
939
- return
940
- }
941
- h, err := protocol.DecodeHeader(recvBuf[:mlen])
942
- if err != nil {
943
- return
944
- }
945
- hdr = h
946
- payload = recvBuf[protocol.HeaderSize:mlen]
947
- } else {
948
- // Named Pipe path
949
- ready, waitErr := session.WaitReadable(serverPollTimeoutMs)
950
- if waitErr != nil {
951
- return
952
- }
953
- if !ready {
954
- continue
955
- }
956
- h, p, err := session.Receive(recvBuf)
957
- if err != nil {
958
- return
959
- }
960
- hdr = h
961
- payload = p
962
- }
963
-
964
- // Protocol violation: unexpected message kind terminates session
965
- if hdr.Kind != protocol.KindRequest {
966
- return
967
- }
968
-
969
- if len(payload) <= int(^uint32(0)) {
970
- serverNotePayloadCapacity(&s.learnedRequestPayloadBytes, uint32(len(payload)))
971
- }
972
-
973
- if !s.methodSupported(hdr.Code) {
974
- respHdr := protocol.Header{
975
- Kind: protocol.KindResponse,
976
- Code: hdr.Code,
977
- MessageID: hdr.MessageID,
978
- TransportStatus: protocol.StatusUnsupported,
979
- ItemCount: 1,
980
- }
981
-
982
- if shm != nil {
983
- if len(msgBuf) < protocol.HeaderSize {
984
- msgBuf = make([]byte, protocol.HeaderSize)
985
- }
986
- msg := msgBuf[:protocol.HeaderSize]
987
- respHdr.Magic = protocol.MagicMsg
988
- respHdr.Version = protocol.Version
989
- respHdr.HeaderLen = protocol.HeaderLen
990
- respHdr.PayloadLen = 0
991
- respHdr.Encode(msg[:protocol.HeaderSize])
992
- if err := shm.WinShmSend(msg); err != nil {
993
- return
994
- }
995
- } else if err := session.Send(&respHdr, nil); err != nil {
996
- return
997
- }
998
- continue
999
- }
1000
-
1001
- // Dispatch: single-item or batch
1002
- responseLen := 0
1003
- isBatch := (hdr.Flags&protocol.FlagBatch != 0) && hdr.ItemCount >= 1
1004
- var dispatchErr error
1005
-
1006
- if !isBatch {
1007
- var derr error
1008
- responseLen, derr = s.dispatchSingle(hdr.Code, payload, respBuf)
1009
- if derr != nil {
1010
- dispatchErr = derr
1011
- responseLen = 0
1012
- } else if responseLen < 0 || responseLen > len(respBuf) {
1013
- dispatchErr = protocol.ErrOverflow
1014
- responseLen = 0
1015
- }
1016
- } else {
1017
- var bb protocol.BatchBuilder
1018
- bb.Reset(respBuf, hdr.ItemCount)
1019
-
1020
- for i := uint32(0); i < hdr.ItemCount && dispatchErr == nil; i++ {
1021
- itemData, gerr := protocol.BatchItemGet(payload, hdr.ItemCount, i)
1022
- if gerr != nil {
1023
- dispatchErr = gerr
1024
- break
1025
- }
1026
-
1027
- itemResultLen, derr := s.dispatchSingle(hdr.Code, itemData, itemRespBuf)
1028
- if derr != nil {
1029
- dispatchErr = derr
1030
- break
1031
- }
1032
- if itemResultLen < 0 || itemResultLen > len(itemRespBuf) {
1033
- dispatchErr = protocol.ErrOverflow
1034
- break
1035
- }
1036
-
1037
- if aerr := bb.Add(itemRespBuf[:itemResultLen]); aerr != nil {
1038
- dispatchErr = aerr
1039
- break
1040
- }
1041
- }
1042
-
1043
- if dispatchErr == nil {
1044
- responseLen, _ = bb.Finish()
1045
- }
1046
- }
1047
-
1048
- // Build response header
1049
- respHdr := protocol.Header{
1050
- Kind: protocol.KindResponse,
1051
- Code: hdr.Code,
1052
- MessageID: hdr.MessageID,
1053
- }
1054
-
1055
- if dispatchErr == nil {
1056
- if responseLen <= int(^uint32(0)) {
1057
- serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, uint32(responseLen))
1058
- }
1059
- respHdr.TransportStatus = protocol.StatusOK
1060
- if isBatch {
1061
- respHdr.Flags = protocol.FlagBatch
1062
- respHdr.ItemCount = hdr.ItemCount
1063
- } else {
1064
- respHdr.ItemCount = 1
1065
- }
1066
- } else if errors.Is(dispatchErr, protocol.ErrOverflow) {
1067
- current := session.MaxResponsePayloadBytes
1068
- if current >= ^uint32(0)/2 {
1069
- serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, ^uint32(0))
1070
- } else {
1071
- serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, current*2)
1072
- }
1073
- respHdr.TransportStatus = protocol.StatusLimitExceeded
1074
- respHdr.ItemCount = 1
1075
- responseLen = 0
1076
- } else if errors.Is(dispatchErr, errHandlerFailed) {
1077
- respHdr.TransportStatus = protocol.StatusInternalError
1078
- respHdr.ItemCount = 1
1079
- responseLen = 0
1080
- } else {
1081
- respHdr.TransportStatus = protocol.StatusBadEnvelope
1082
- respHdr.ItemCount = 1
1083
- responseLen = 0
1084
- }
1085
-
1086
- if shm != nil {
1087
- msgLen := protocol.HeaderSize + responseLen
1088
- if len(msgBuf) < msgLen {
1089
- msgBuf = make([]byte, msgLen)
1090
- }
1091
- msg := msgBuf[:msgLen]
1092
-
1093
- respHdr.Magic = protocol.MagicMsg
1094
- respHdr.Version = protocol.Version
1095
- respHdr.HeaderLen = protocol.HeaderLen
1096
- respHdr.PayloadLen = uint32(responseLen)
1097
-
1098
- respHdr.Encode(msg[:protocol.HeaderSize])
1099
- if responseLen > 0 {
1100
- copy(msg[protocol.HeaderSize:], respBuf[:responseLen])
1101
- }
1102
-
1103
- if err := shm.WinShmSend(msg); err != nil {
1104
- return
1105
- }
1106
- if respHdr.TransportStatus == protocol.StatusLimitExceeded {
1107
- return
1108
- }
1109
- } else {
1110
- if err := session.Send(&respHdr, respBuf[:responseLen]); err != nil {
1111
- return
1112
- }
1113
- if respHdr.TransportStatus == protocol.StatusLimitExceeded {
1114
- return
1115
- }
1116
- }
1117
- }
1118
-}
1119
-
1120
-// Suppress unused import warnings.
1121
-var _ = binary.NativeEndian
src/go/pkg/netipc/service/raw/dispatch.go
new
+9
@@ -0,0 +1,9 @@
1
+package raw
2
+
3
+import "errors"
4
+
5
+// DispatchHandler validates/decodes a single service kind request and writes
6
+// the matching response into responseBuf.
7
+type DispatchHandler func(request []byte, responseBuf []byte) (int, error)
8
+
9
+var errHandlerFailed = errors.New("dispatch handler failed")
src/go/pkg/netipc/service/raw/increment.go
new
+179
@@ -0,0 +1,179 @@
1
+package raw
2
+
3
+import "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
4
+
5
+// IncrementHandler serves a single INCREMENT service kind.
6
+type IncrementHandler func(uint64) (uint64, bool)
7
+
8
+// CallIncrement performs a blocking INCREMENT call.
9
+// Sends requestValue, returns the server's response value.
10
+func (c *Client) CallIncrement(requestValue uint64) (uint64, error) {
11
+ if err := c.validateMethod(protocol.MethodIncrement); err != nil {
12
+ return 0, err
13
+ }
14
+
15
+ var result uint64
16
+
17
+ err := c.callWithRetry(func() error {
18
+ var reqBuf [protocol.IncrementPayloadSize]byte
19
+ if protocol.IncrementEncode(requestValue, reqBuf[:]) == 0 {
20
+ return protocol.ErrTruncated
21
+ }
22
+
23
+ _, payload, rerr := c.doRawCall(protocol.MethodIncrement, reqBuf[:])
24
+ if rerr != nil {
25
+ return rerr
26
+ }
27
+
28
+ val, derr := protocol.IncrementDecode(payload)
29
+ if derr != nil {
30
+ return derr
31
+ }
32
+ result = val
33
+ return nil
34
+ })
35
+ return result, err
36
+}
37
+
38
+// CallIncrementBatch performs a blocking batch INCREMENT call.
39
+// Sends multiple values, returns the server's response values.
40
+func (c *Client) CallIncrementBatch(values []uint64) ([]uint64, error) {
41
+ if err := c.validateMethod(protocol.MethodIncrement); err != nil {
42
+ return nil, err
43
+ }
44
+
45
+ if len(values) == 0 {
46
+ return nil, nil
47
+ }
48
+
49
+ var results []uint64
50
+ itemCount, err := checkedLookupU32(len(values))
51
+ if err != nil {
52
+ return nil, err
53
+ }
54
+
55
+ err = c.callWithRetry(func() error {
56
+ dirBytes, err := checkedLookupMul(len(values), 8)
57
+ if err != nil {
58
+ return err
59
+ }
60
+ dirAligned, err := checkedLookupAlign8(dirBytes)
61
+ if err != nil {
62
+ return err
63
+ }
64
+ itemsBytes, err := checkedLookupMul(len(values), protocol.IncrementPayloadSize)
65
+ if err != nil {
66
+ return err
67
+ }
68
+ paddingBytes, err := checkedLookupMul(len(values), protocol.Alignment)
69
+ if err != nil {
70
+ return err
71
+ }
72
+ batchBufSize, err := checkedLookupAdd(dirAligned, itemsBytes)
73
+ if err != nil {
74
+ return err
75
+ }
76
+ batchBufSize, err = checkedLookupAdd(batchBufSize, paddingBytes)
77
+ if err != nil {
78
+ return err
79
+ }
80
+ batchBuf := ensureClientScratch(&c.requestBuf, batchBufSize)
81
+ bb := protocol.NewBatchBuilder(batchBuf, itemCount)
82
+
83
+ for _, v := range values {
84
+ var item [protocol.IncrementPayloadSize]byte
85
+ if protocol.IncrementEncode(v, item[:]) == 0 {
86
+ return protocol.ErrTruncated
87
+ }
88
+ if err := bb.Add(item[:]); err != nil {
89
+ return err
90
+ }
91
+ }
92
+
93
+ totalPayloadLen, _ := bb.Finish()
94
+ reqPayload := batchBuf[:totalPayloadLen]
95
+
96
+ hdr := protocol.Header{
97
+ Kind: protocol.KindRequest,
98
+ Code: protocol.MethodIncrement,
99
+ Flags: protocol.FlagBatch,
100
+ ItemCount: itemCount,
101
+ MessageID: uint64(c.callCount) + 1,
102
+ TransportStatus: protocol.StatusOK,
103
+ }
104
+
105
+ if err := c.transportSend(&hdr, reqPayload); err != nil {
106
+ return err
107
+ }
108
+
109
+ respHdr, respPayload, err := c.transportReceive()
110
+ if err != nil {
111
+ return err
112
+ }
113
+
114
+ if respHdr.Kind != protocol.KindResponse {
115
+ return protocol.ErrBadKind
116
+ }
117
+ if respHdr.Code != protocol.MethodIncrement {
118
+ return protocol.ErrBadLayout
119
+ }
120
+ if respHdr.MessageID != hdr.MessageID {
121
+ return protocol.ErrBadLayout
122
+ }
123
+ switch respHdr.TransportStatus {
124
+ case protocol.StatusOK:
125
+ case protocol.StatusLimitExceeded:
126
+ if current := c.sessionMaxResponsePayloadBytes(); current > 0 {
127
+ if current >= ^uint32(0)/2 {
128
+ c.noteResponseCapacity(^uint32(0))
129
+ } else {
130
+ c.noteResponseCapacity(current * 2)
131
+ }
132
+ }
133
+ return protocol.ErrOverflow
134
+ default:
135
+ return protocol.ErrBadLayout
136
+ }
137
+ if respHdr.Flags&protocol.FlagBatch == 0 || respHdr.ItemCount != itemCount {
138
+ return protocol.ErrBadItemCount
139
+ }
140
+
141
+ out := make([]uint64, itemCount)
142
+ for i := range itemCount {
143
+ itemData, gerr := protocol.BatchItemGet(respPayload, itemCount, i)
144
+ if gerr != nil {
145
+ return gerr
146
+ }
147
+ val, derr := protocol.IncrementDecode(itemData)
148
+ if derr != nil {
149
+ return derr
150
+ }
151
+ out[i] = val
152
+ }
153
+ results = out
154
+ return nil
155
+ })
156
+ return results, err
157
+}
158
+
159
+// IncrementDispatch adapts a typed increment handler to the raw dispatch shape.
160
+func IncrementDispatch(handle IncrementHandler) DispatchHandler {
161
+ if handle == nil {
162
+ return nil
163
+ }
164
+ return func(request []byte, responseBuf []byte) (int, error) {
165
+ value, err := protocol.IncrementDecode(request)
166
+ if err != nil {
167
+ return 0, err
168
+ }
169
+ result, ok := handle(value)
170
+ if !ok {
171
+ return 0, errHandlerFailed
172
+ }
173
+ n := protocol.IncrementEncode(result, responseBuf)
174
+ if n == 0 {
175
+ return 0, protocol.ErrOverflow
176
+ }
177
+ return n, nil
178
+ }
179
+}
src/go/pkg/netipc/service/raw/increment_unix.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
+)
9
+
10
+// NewIncrementClient creates a raw client bound to the increment service kind.
11
+func NewIncrementClient(runDir, serviceName string, config posix.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodIncrement)
13
+}
src/go/pkg/netipc/service/raw/increment_windows.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build windows
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
8
+)
9
+
10
+// NewIncrementClient creates a raw client bound to the increment service kind.
11
+func NewIncrementClient(runDir, serviceName string, config windows.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodIncrement)
13
+}
src/go/pkg/netipc/service/raw/lookup_common.go
new
+55
@@ -0,0 +1,55 @@
1
+package raw
2
+
3
+import "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
4
+
5
+func checkedLookupAdd(a, b int) (int, error) {
6
+ if a < 0 || b < 0 {
7
+ return 0, protocol.ErrOverflow
8
+ }
9
+ maxInt := int(^uint(0) >> 1)
10
+ if a > maxInt-b {
11
+ return 0, protocol.ErrOverflow
12
+ }
13
+ return a + b, nil
14
+}
15
+
16
+func checkedLookupMul(a, b int) (int, error) {
17
+ if a < 0 || b < 0 {
18
+ return 0, protocol.ErrOverflow
19
+ }
20
+ maxInt := int(^uint(0) >> 1)
21
+ if a != 0 && b > maxInt/a {
22
+ return 0, protocol.ErrOverflow
23
+ }
24
+ return a * b, nil
25
+}
26
+
27
+func checkedLookupAlign8(v int) (int, error) {
28
+ if v < 0 {
29
+ return 0, protocol.ErrOverflow
30
+ }
31
+ maxInt := int(^uint(0) >> 1)
32
+ if v > maxInt-7 {
33
+ return 0, protocol.ErrOverflow
34
+ }
35
+ return protocol.Align8(v), nil
36
+}
37
+
38
+func checkedLookupU32(value int) (uint32, error) {
39
+ if value < 0 || uint64(value) > uint64(^uint32(0)) {
40
+ return 0, protocol.ErrOverflow
41
+ }
42
+ return uint32(value), nil // #nosec G115 -- value is bounded by the uint32 maximum above.
43
+}
44
+
45
+func lookupMinRequired(hdrSize int, itemCount uint32) (int, error) {
46
+ if hdrSize < 0 {
47
+ return 0, protocol.ErrOverflow
48
+ }
49
+ dirSize64 := uint64(itemCount) * uint64(protocol.LookupDirEntrySize)
50
+ min64 := uint64(hdrSize) + dirSize64
51
+ if min64 > uint64(int(^uint(0)>>1)) {
52
+ return 0, protocol.ErrOverflow
53
+ }
54
+ return int(min64), nil
55
+}
src/go/pkg/netipc/service/raw/lookup_unix_test.go
new
+407
@@ -0,0 +1,407 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "fmt"
7
+ "sync"
8
+ "testing"
9
+ "time"
10
+
11
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
12
+)
13
+
14
+func testCgroupsLookupHandler(req *protocol.CgroupsLookupRequestView, builder *protocol.CgroupsLookupBuilder) bool {
15
+ for i := uint32(0); i < req.ItemCount; i++ {
16
+ path, err := req.Item(i)
17
+ if err != nil {
18
+ return false
19
+ }
20
+ if path.String() == "/known" {
21
+ if err := builder.Add(
22
+ protocol.CgroupLookupKnown,
23
+ protocol.OrchestratorK8s,
24
+ path.Bytes(),
25
+ []byte("pod-a"),
26
+ []struct{ Key, Value []byte }{
27
+ {Key: []byte("namespace"), Value: []byte("default")},
28
+ },
29
+ ); err != nil {
30
+ return false
31
+ }
32
+ } else {
33
+ if err := builder.Add(
34
+ protocol.CgroupLookupUnknownRetryLater,
35
+ 0,
36
+ path.Bytes(),
37
+ nil,
38
+ nil,
39
+ ); err != nil {
40
+ return false
41
+ }
42
+ }
43
+ }
44
+ return true
45
+}
46
+
47
+func testAppsLookupHandler(req *protocol.AppsLookupRequestView, builder *protocol.AppsLookupBuilder) bool {
48
+ for i := uint32(0); i < req.ItemCount; i++ {
49
+ pid, err := req.Item(i)
50
+ if err != nil {
51
+ return false
52
+ }
53
+ switch pid {
54
+ case 1234:
55
+ if err := builder.Add(
56
+ protocol.PidLookupKnown,
57
+ protocol.AppsCgroupKnown,
58
+ protocol.OrchestratorDocker,
59
+ pid,
60
+ 1,
61
+ 1000,
62
+ 42,
63
+ []byte("nginx"),
64
+ []byte("/docker/abc"),
65
+ []byte("container-a"),
66
+ []struct{ Key, Value []byte }{
67
+ {Key: []byte("image"), Value: []byte("nginx:latest")},
68
+ },
69
+ ); err != nil {
70
+ return false
71
+ }
72
+ case 0:
73
+ if err := builder.Add(
74
+ protocol.PidLookupKnown,
75
+ protocol.AppsCgroupHostRoot,
76
+ 0,
77
+ pid,
78
+ 0,
79
+ 0,
80
+ 0,
81
+ []byte("swapper"),
82
+ nil,
83
+ nil,
84
+ nil,
85
+ ); err != nil {
86
+ return false
87
+ }
88
+ default:
89
+ if err := builder.Add(
90
+ protocol.PidLookupUnknown,
91
+ protocol.AppsCgroupKnown,
92
+ 0,
93
+ pid,
94
+ 0,
95
+ protocol.NipcUIDUnset,
96
+ 0,
97
+ nil,
98
+ nil,
99
+ nil,
100
+ nil,
101
+ ); err != nil {
102
+ return false
103
+ }
104
+ }
105
+ }
106
+ return true
107
+}
108
+
109
+func startLookupTestServer(service string, method uint16, handler DispatchHandler) *testServer {
110
+ return startLookupTestServerWithWorkers(service, method, handler, 8)
111
+}
112
+
113
+func startLookupTestServerWithWorkers(service string, method uint16, handler DispatchHandler, workers int) *testServer {
114
+ ensureRunDir()
115
+ cleanupAll(service)
116
+
117
+ s := NewServerWithWorkers(testRunDir, service, testServerConfig(), method, handler, workers)
118
+ doneCh := make(chan struct{})
119
+
120
+ go func() {
121
+ defer close(doneCh)
122
+ s.Run()
123
+ }()
124
+
125
+ waitUnixServerReady(service)
126
+ return &testServer{server: s, doneCh: doneCh}
127
+}
128
+
129
+func verifyCgroupsLookupView(t *testing.T, view *protocol.CgroupsLookupResponseView) {
130
+ t.Helper()
131
+ if err := checkCgroupsLookupView(view); err != nil {
132
+ t.Fatal(err)
133
+ }
134
+}
135
+
136
+func checkCgroupsLookupView(view *protocol.CgroupsLookupResponseView) error {
137
+ if view.ItemCount != 2 {
138
+ return fmt.Errorf("item count = %d, want 2", view.ItemCount)
139
+ }
140
+ item0, err := view.Item(0)
141
+ if err != nil {
142
+ return fmt.Errorf("item 0: %w", err)
143
+ }
144
+ if item0.Status != protocol.CgroupLookupKnown ||
145
+ item0.Orchestrator != protocol.OrchestratorK8s ||
146
+ item0.Path.String() != "/known" ||
147
+ item0.Name.String() != "pod-a" ||
148
+ item0.LabelCount != 1 {
149
+ return fmt.Errorf("bad item 0: %+v", item0)
150
+ }
151
+ item1, err := view.Item(1)
152
+ if err != nil {
153
+ return fmt.Errorf("item 1: %w", err)
154
+ }
155
+ if item1.Status != protocol.CgroupLookupUnknownRetryLater || item1.Path.String() != "/missing" {
156
+ return fmt.Errorf("bad item 1: %+v", item1)
157
+ }
158
+ return nil
159
+}
160
+
161
+func verifyAppsLookupView(t *testing.T, view *protocol.AppsLookupResponseView) {
162
+ t.Helper()
163
+ if err := checkAppsLookupView(view); err != nil {
164
+ t.Fatal(err)
165
+ }
166
+}
167
+
168
+func checkAppsLookupView(view *protocol.AppsLookupResponseView) error {
169
+ if view.ItemCount != 3 {
170
+ return fmt.Errorf("item count = %d, want 3", view.ItemCount)
171
+ }
172
+ item0, err := view.Item(0)
173
+ if err != nil {
174
+ return fmt.Errorf("item 0: %w", err)
175
+ }
176
+ if item0.Pid != 1234 ||
177
+ item0.Status != protocol.PidLookupKnown ||
178
+ item0.CgroupStatus != protocol.AppsCgroupKnown ||
179
+ item0.Comm.String() != "nginx" ||
180
+ item0.CgroupPath.String() != "/docker/abc" ||
181
+ item0.LabelCount != 1 {
182
+ return fmt.Errorf("bad item 0: %+v", item0)
183
+ }
184
+ item1, err := view.Item(1)
185
+ if err != nil {
186
+ return fmt.Errorf("item 1: %w", err)
187
+ }
188
+ if item1.Pid != 0 || item1.CgroupStatus != protocol.AppsCgroupHostRoot || item1.CgroupPath.Len() != 0 {
189
+ return fmt.Errorf("bad item 1: %+v", item1)
190
+ }
191
+ item2, err := view.Item(2)
192
+ if err != nil {
193
+ return fmt.Errorf("item 2: %w", err)
194
+ }
195
+ if item2.Pid != 9999 || item2.Status != protocol.PidLookupUnknown || item2.Uid != protocol.NipcUIDUnset {
196
+ return fmt.Errorf("bad item 2: %+v", item2)
197
+ }
198
+ return nil
199
+}
200
+
201
+func TestCgroupsLookupCall(t *testing.T) {
202
+ svc := "go_svc_cgroups_lookup"
203
+ ts := startLookupTestServer(svc, protocol.MethodCgroupsLookup, CgroupsLookupDispatch(testCgroupsLookupHandler))
204
+ defer ts.stop()
205
+
206
+ client := NewCgroupsLookupClient(testRunDir, svc, testClientConfig())
207
+ client.Refresh()
208
+ if !client.Ready() {
209
+ t.Fatal("client not ready")
210
+ }
211
+
212
+ view, err := client.CallCgroupsLookup([][]byte{[]byte("/known"), []byte("/missing")})
213
+ if err != nil {
214
+ t.Fatalf("call failed: %v", err)
215
+ }
216
+ verifyCgroupsLookupView(t, view)
217
+}
218
+
219
+func TestAppsLookupCall(t *testing.T) {
220
+ svc := "go_svc_apps_lookup"
221
+ ts := startLookupTestServer(svc, protocol.MethodAppsLookup, AppsLookupDispatch(testAppsLookupHandler))
222
+ defer ts.stop()
223
+
224
+ client := NewAppsLookupClient(testRunDir, svc, testClientConfig())
225
+ client.Refresh()
226
+ if !client.Ready() {
227
+ t.Fatal("client not ready")
228
+ }
229
+
230
+ view, err := client.CallAppsLookup([]uint32{1234, 0, 9999})
231
+ if err != nil {
232
+ t.Fatalf("call failed: %v", err)
233
+ }
234
+ verifyAppsLookupView(t, view)
235
+}
236
+
237
+func TestLookupHandlerFailures(t *testing.T) {
238
+ t.Run("cgroups", func(t *testing.T) {
239
+ svc := uniqueUnixService("go_svc_cgroups_lookup_fail")
240
+ ts := startLookupTestServer(svc, protocol.MethodCgroupsLookup, CgroupsLookupDispatch(
241
+ func(*protocol.CgroupsLookupRequestView, *protocol.CgroupsLookupBuilder) bool { return false },
242
+ ))
243
+ defer ts.stop()
244
+
245
+ client := NewCgroupsLookupClient(testRunDir, svc, testClientConfig())
246
+ defer client.Close()
247
+ client.Refresh()
248
+ if !client.Ready() {
249
+ t.Fatal("client not ready")
250
+ }
251
+ if _, err := client.CallCgroupsLookup([][]byte{[]byte("/known")}); err == nil {
252
+ t.Fatal("expected cgroups lookup handler failure")
253
+ }
254
+ })
255
+
256
+ t.Run("apps", func(t *testing.T) {
257
+ svc := uniqueUnixService("go_svc_apps_lookup_fail")
258
+ ts := startLookupTestServer(svc, protocol.MethodAppsLookup, AppsLookupDispatch(
259
+ func(*protocol.AppsLookupRequestView, *protocol.AppsLookupBuilder) bool { return false },
260
+ ))
261
+ defer ts.stop()
262
+
263
+ client := NewAppsLookupClient(testRunDir, svc, testClientConfig())
264
+ defer client.Close()
265
+ client.Refresh()
266
+ if !client.Ready() {
267
+ t.Fatal("client not ready")
268
+ }
269
+ if _, err := client.CallAppsLookup([]uint32{1234}); err == nil {
270
+ t.Fatal("expected apps lookup handler failure")
271
+ }
272
+ })
273
+}
274
+
275
+func TestCgroupsLookupRetryOnFailure(t *testing.T) {
276
+ svc := uniqueUnixService("go_svc_cgroups_lookup_retry")
277
+ ts1 := startLookupTestServer(svc, protocol.MethodCgroupsLookup, CgroupsLookupDispatch(testCgroupsLookupHandler))
278
+
279
+ client := NewCgroupsLookupClient(testRunDir, svc, testClientConfig())
280
+ defer client.Close()
281
+ client.Refresh()
282
+ if !client.Ready() {
283
+ t.Fatal("client not ready")
284
+ }
285
+
286
+ view, err := client.CallCgroupsLookup([][]byte{[]byte("/known"), []byte("/missing")})
287
+ if err != nil {
288
+ t.Fatalf("first call failed: %v", err)
289
+ }
290
+ verifyCgroupsLookupView(t, view)
291
+
292
+ ts1.stop()
293
+ cleanupAll(svc)
294
+ time.Sleep(50 * time.Millisecond)
295
+
296
+ ts2 := startLookupTestServer(svc, protocol.MethodCgroupsLookup, CgroupsLookupDispatch(testCgroupsLookupHandler))
297
+ defer ts2.stop()
298
+
299
+ view, err = client.CallCgroupsLookup([][]byte{[]byte("/known"), []byte("/missing")})
300
+ if err != nil {
301
+ t.Fatalf("retry call failed: %v", err)
302
+ }
303
+ verifyCgroupsLookupView(t, view)
304
+ if client.Status().ReconnectCount < 1 {
305
+ t.Fatalf("expected reconnect_count >= 1, got %d", client.Status().ReconnectCount)
306
+ }
307
+}
308
+
309
+func TestLookupConcurrentClients(t *testing.T) {
310
+ t.Run("cgroups", func(t *testing.T) {
311
+ svc := uniqueUnixService("go_svc_cgroups_lookup_concurrent")
312
+ ts := startLookupTestServerWithWorkers(
313
+ svc,
314
+ protocol.MethodCgroupsLookup,
315
+ CgroupsLookupDispatch(testCgroupsLookupHandler),
316
+ 16,
317
+ )
318
+ defer ts.stop()
319
+
320
+ const clients = 16
321
+ const callsPerClient = 10
322
+ var wg sync.WaitGroup
323
+ errs := make(chan error, clients*callsPerClient)
324
+ for range clients {
325
+ wg.Go(func() {
326
+ client := NewCgroupsLookupClient(testRunDir, svc, testClientConfig())
327
+ defer client.Close()
328
+ for range 200 {
329
+ client.Refresh()
330
+ if client.Ready() {
331
+ break
332
+ }
333
+ time.Sleep(5 * time.Millisecond)
334
+ }
335
+ if !client.Ready() {
336
+ errs <- fmt.Errorf("client not ready")
337
+ return
338
+ }
339
+ for range callsPerClient {
340
+ view, err := client.CallCgroupsLookup([][]byte{[]byte("/known"), []byte("/missing")})
341
+ if err != nil {
342
+ errs <- err
343
+ return
344
+ }
345
+ if err := checkCgroupsLookupView(view); err != nil {
346
+ errs <- err
347
+ return
348
+ }
349
+ }
350
+ })
351
+ }
352
+ wg.Wait()
353
+ close(errs)
354
+ for err := range errs {
355
+ t.Fatalf("concurrent cgroups lookup failed: %v", err)
356
+ }
357
+ })
358
+
359
+ t.Run("apps", func(t *testing.T) {
360
+ svc := uniqueUnixService("go_svc_apps_lookup_concurrent")
361
+ ts := startLookupTestServerWithWorkers(
362
+ svc,
363
+ protocol.MethodAppsLookup,
364
+ AppsLookupDispatch(testAppsLookupHandler),
365
+ 16,
366
+ )
367
+ defer ts.stop()
368
+
369
+ const clients = 16
370
+ const callsPerClient = 10
371
+ var wg sync.WaitGroup
372
+ errs := make(chan error, clients*callsPerClient)
373
+ for range clients {
374
+ wg.Go(func() {
375
+ client := NewAppsLookupClient(testRunDir, svc, testClientConfig())
376
+ defer client.Close()
377
+ for range 200 {
378
+ client.Refresh()
379
+ if client.Ready() {
380
+ break
381
+ }
382
+ time.Sleep(5 * time.Millisecond)
383
+ }
384
+ if !client.Ready() {
385
+ errs <- fmt.Errorf("client not ready")
386
+ return
387
+ }
388
+ for range callsPerClient {
389
+ view, err := client.CallAppsLookup([]uint32{1234, 0, 9999})
390
+ if err != nil {
391
+ errs <- err
392
+ return
393
+ }
394
+ if err := checkAppsLookupView(view); err != nil {
395
+ errs <- err
396
+ return
397
+ }
398
+ }
399
+ })
400
+ }
401
+ wg.Wait()
402
+ close(errs)
403
+ for err := range errs {
404
+ t.Fatalf("concurrent apps lookup failed: %v", err)
405
+ }
406
+ })
407
+}
src/go/pkg/netipc/service/raw/poll_unix.go
new
+65
@@ -0,0 +1,65 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "syscall"
7
+ "unsafe"
8
+)
9
+
10
+// poll constants (not exported by Go's syscall package)
11
+const (
12
+ _POLLIN = 0x0001
13
+ _POLLERR = 0x0008
14
+ _POLLHUP = 0x0010
15
+ _POLLNVAL = 0x0020
16
+)
17
+
18
+// pollfd matches struct pollfd from <poll.h>.
19
+type pollfd struct {
20
+ fd int32
21
+ events int16
22
+ revents int16
23
+}
24
+
25
+// pollFd polls a file descriptor for readability with a timeout in ms.
26
+// Returns: 1 = data ready, 0 = timeout, -1 = error/hangup.
27
+func pollFd(fd int, timeoutMs int) int {
28
+ const maxInt32 = 1<<31 - 1
29
+ if fd < 0 || fd > maxInt32 {
30
+ return -1
31
+ }
32
+ pfd := pollfd{
33
+ fd: int32(fd), // #nosec G115 -- fd is checked against int32 range above.
34
+ events: _POLLIN,
35
+ }
36
+
37
+ r, _, errno := syscall.Syscall(
38
+ syscall.SYS_POLL,
39
+ uintptr(unsafe.Pointer(&pfd)), // #nosec G103 -- raw poll syscall requires a pollfd pointer.
40
+ 1,
41
+ uintptr(timeoutMs),
42
+ )
43
+
44
+ n := int(r)
45
+ if n < 0 {
46
+ if errno == syscall.EINTR {
47
+ return 0
48
+ }
49
+ return -1
50
+ }
51
+
52
+ if n == 0 {
53
+ return 0
54
+ }
55
+
56
+ if pfd.revents&(_POLLERR|_POLLHUP|_POLLNVAL) != 0 {
57
+ return -1
58
+ }
59
+
60
+ if pfd.revents&_POLLIN != 0 {
61
+ return 1
62
+ }
63
+
64
+ return 0
65
+}
src/go/pkg/netipc/service/raw/server.go
new
+285
@@ -0,0 +1,285 @@
1
+package raw
2
+
3
+import (
4
+ "errors"
5
+ "sync/atomic"
6
+ "time"
7
+
8
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
9
+)
10
+
11
+const defaultServerWorkerCount = 8
12
+
13
+func (s *Server) initCommon(
14
+ runDir string,
15
+ serviceName string,
16
+ expectedMethodCode uint16,
17
+ handler DispatchHandler,
18
+ workerCount int,
19
+ maxRequestPayloadBytes uint32,
20
+ maxResponsePayloadBytes uint32,
21
+) {
22
+ if workerCount < 1 {
23
+ workerCount = 1
24
+ }
25
+ if maxRequestPayloadBytes == 0 {
26
+ maxRequestPayloadBytes = protocol.MaxPayloadDefault
27
+ }
28
+ if maxResponsePayloadBytes == 0 {
29
+ maxResponsePayloadBytes = protocol.MaxPayloadDefault
30
+ }
31
+
32
+ s.runDir = runDir
33
+ s.serviceName = serviceName
34
+ s.expectedMethodCode = expectedMethodCode
35
+ s.handler = handler
36
+ s.workerCount = workerCount
37
+ s.learnedRequestPayloadBytes.Store(maxRequestPayloadBytes)
38
+ s.learnedResponsePayloadBytes.Store(maxResponsePayloadBytes)
39
+}
40
+
41
+func (s *Server) retryAcceptAfter(cleanup func()) bool {
42
+ if cleanup != nil {
43
+ cleanup()
44
+ }
45
+ if !s.running.Load() {
46
+ return false
47
+ }
48
+ time.Sleep(10 * time.Millisecond)
49
+ return true
50
+}
51
+
52
+func (s *Server) acquireWorkerSlot(sem chan struct{}, cleanup func(), closeSession func()) bool {
53
+ select {
54
+ case sem <- struct{}{}:
55
+ return true
56
+ default:
57
+ if cleanup != nil {
58
+ cleanup()
59
+ }
60
+ closeSession()
61
+ return false
62
+ }
63
+}
64
+
65
+func (s *Server) startSessionWorker(sem chan struct{}, run func()) {
66
+ s.wg.Add(1)
67
+ go func() {
68
+ defer func() {
69
+ if recover() != nil {
70
+ // Session handler panicked; keep the accept loop alive.
71
+ }
72
+ <-sem
73
+ s.wg.Done()
74
+ }()
75
+ run()
76
+ }()
77
+}
78
+
79
+func (s *Server) dispatchSingle(methodCode uint16, request []byte, responseBuf []byte) (int, error) {
80
+ if methodCode != s.expectedMethodCode || s.handler == nil {
81
+ return 0, errHandlerFailed
82
+ }
83
+
84
+ return s.handler(request, responseBuf)
85
+}
86
+
87
+func (s *Server) methodSupported(methodCode uint16) bool {
88
+ return s.handler != nil && methodCode == s.expectedMethodCode
89
+}
90
+
91
+func serverNotePayloadCapacity(target *atomic.Uint32, payloadLen uint32) {
92
+ grown := nextPowerOf2U32(payloadLen)
93
+ for {
94
+ current := target.Load()
95
+ if grown <= current {
96
+ return
97
+ }
98
+ if target.CompareAndSwap(current, grown) {
99
+ return
100
+ }
101
+ }
102
+}
103
+
104
+type serverReceiveAction uint8
105
+
106
+const (
107
+ serverReceiveOK serverReceiveAction = iota
108
+ serverReceiveContinue
109
+ serverReceiveStop
110
+)
111
+
112
+type serverSessionOps struct {
113
+ maxRequestPayloadBytes uint32
114
+ maxResponsePayloadBytes uint32
115
+ receive func([]byte) (protocol.Header, []byte, serverReceiveAction)
116
+ send func(*protocol.Header, []byte, *[]byte) error
117
+ close func()
118
+}
119
+
120
+func (s *Server) handleServerSession(ops serverSessionOps) {
121
+ recvBuf := make([]byte, protocol.HeaderSize+int(ops.maxRequestPayloadBytes))
122
+ respBuf := make([]byte, int(ops.maxResponsePayloadBytes))
123
+ itemRespBuf := make([]byte, int(ops.maxResponsePayloadBytes))
124
+ msgBuf := make([]byte, int(ops.maxResponsePayloadBytes)+protocol.HeaderSize)
125
+
126
+ defer ops.close()
127
+
128
+ for s.running.Load() {
129
+ hdr, payload, action := ops.receive(recvBuf)
130
+ switch action {
131
+ case serverReceiveContinue:
132
+ continue
133
+ case serverReceiveStop:
134
+ return
135
+ }
136
+
137
+ if hdr.Kind != protocol.KindRequest {
138
+ return
139
+ }
140
+
141
+ respHdr, responseLen, closeAfterSend := s.handleServerRequest(
142
+ hdr, payload, respBuf, itemRespBuf, ops.maxResponsePayloadBytes)
143
+ if err := ops.send(&respHdr, respBuf[:responseLen], &msgBuf); err != nil {
144
+ return
145
+ }
146
+ if closeAfterSend {
147
+ return
148
+ }
149
+ }
150
+}
151
+
152
+func (s *Server) handleServerRequest(
153
+ hdr protocol.Header,
154
+ payload []byte,
155
+ respBuf []byte,
156
+ itemRespBuf []byte,
157
+ maxResponsePayloadBytes uint32,
158
+) (protocol.Header, int, bool) {
159
+ if payloadLen, err := checkedLookupU32(len(payload)); err == nil {
160
+ serverNotePayloadCapacity(&s.learnedRequestPayloadBytes, payloadLen)
161
+ }
162
+
163
+ if !s.methodSupported(hdr.Code) {
164
+ return serverUnsupportedResponseHeader(hdr), 0, false
165
+ }
166
+
167
+ responseLen, isBatch, dispatchErr := s.dispatchServerResponse(hdr, payload, respBuf, itemRespBuf)
168
+ respHdr := serverResponseHeader(hdr)
169
+
170
+ if dispatchErr == nil {
171
+ if responseLen32, err := checkedLookupU32(responseLen); err == nil {
172
+ serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, responseLen32)
173
+ }
174
+ respHdr.TransportStatus = protocol.StatusOK
175
+ if isBatch {
176
+ respHdr.Flags = protocol.FlagBatch
177
+ respHdr.ItemCount = hdr.ItemCount
178
+ } else {
179
+ respHdr.ItemCount = 1
180
+ }
181
+ return respHdr, responseLen, false
182
+ }
183
+
184
+ respHdr.ItemCount = 1
185
+ responseLen = 0
186
+ switch {
187
+ case errors.Is(dispatchErr, protocol.ErrOverflow):
188
+ if maxResponsePayloadBytes >= ^uint32(0)/2 {
189
+ serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, ^uint32(0))
190
+ } else {
191
+ serverNotePayloadCapacity(&s.learnedResponsePayloadBytes, maxResponsePayloadBytes*2)
192
+ }
193
+ respHdr.TransportStatus = protocol.StatusLimitExceeded
194
+ return respHdr, responseLen, true
195
+ case errors.Is(dispatchErr, errHandlerFailed):
196
+ respHdr.TransportStatus = protocol.StatusInternalError
197
+ default:
198
+ respHdr.TransportStatus = protocol.StatusBadEnvelope
199
+ }
200
+
201
+ return respHdr, responseLen, false
202
+}
203
+
204
+func (s *Server) dispatchServerResponse(
205
+ hdr protocol.Header,
206
+ payload []byte,
207
+ respBuf []byte,
208
+ itemRespBuf []byte,
209
+) (int, bool, error) {
210
+ isBatch := (hdr.Flags&protocol.FlagBatch != 0) && hdr.ItemCount >= 1
211
+ if !isBatch {
212
+ responseLen, err := s.dispatchSingle(hdr.Code, payload, respBuf)
213
+ if err != nil {
214
+ return 0, false, err
215
+ }
216
+ if responseLen < 0 || responseLen > len(respBuf) {
217
+ return 0, false, protocol.ErrOverflow
218
+ }
219
+ return responseLen, false, nil
220
+ }
221
+
222
+ var bb protocol.BatchBuilder
223
+ bb.Reset(respBuf, hdr.ItemCount)
224
+ for i := uint32(0); i < hdr.ItemCount; i++ {
225
+ itemData, err := protocol.BatchItemGet(payload, hdr.ItemCount, i)
226
+ if err != nil {
227
+ return 0, true, err
228
+ }
229
+
230
+ itemResultLen, err := s.dispatchSingle(hdr.Code, itemData, itemRespBuf)
231
+ if err != nil {
232
+ return 0, true, err
233
+ }
234
+ if itemResultLen < 0 || itemResultLen > len(itemRespBuf) {
235
+ return 0, true, protocol.ErrOverflow
236
+ }
237
+ if err := bb.Add(itemRespBuf[:itemResultLen]); err != nil {
238
+ return 0, true, err
239
+ }
240
+ }
241
+
242
+ responseLen, _ := bb.Finish()
243
+ return responseLen, true, nil
244
+}
245
+
246
+func serverResponseHeader(hdr protocol.Header) protocol.Header {
247
+ return protocol.Header{
248
+ Kind: protocol.KindResponse,
249
+ Code: hdr.Code,
250
+ MessageID: hdr.MessageID,
251
+ }
252
+}
253
+
254
+func serverUnsupportedResponseHeader(hdr protocol.Header) protocol.Header {
255
+ respHdr := serverResponseHeader(hdr)
256
+ respHdr.TransportStatus = protocol.StatusUnsupported
257
+ respHdr.ItemCount = 1
258
+ return respHdr
259
+}
260
+
261
+func serverEncodeSharedResponse(
262
+ respHdr *protocol.Header,
263
+ payload []byte,
264
+ msgBuf *[]byte,
265
+) ([]byte, error) {
266
+ payloadLen, err := checkedLookupU32(len(payload))
267
+ if err != nil {
268
+ return nil, err
269
+ }
270
+ msgLen := protocol.HeaderSize + len(payload)
271
+ if len(*msgBuf) < msgLen {
272
+ *msgBuf = make([]byte, msgLen)
273
+ }
274
+ msg := (*msgBuf)[:msgLen]
275
+
276
+ respHdr.Magic = protocol.MagicMsg
277
+ respHdr.Version = protocol.Version
278
+ respHdr.HeaderLen = protocol.HeaderLen
279
+ respHdr.PayloadLen = payloadLen
280
+ respHdr.Encode(msg[:protocol.HeaderSize])
281
+ if len(payload) > 0 {
282
+ copy(msg[protocol.HeaderSize:], payload)
283
+ }
284
+ return msg, nil
285
+}
src/go/pkg/netipc/service/raw/server_unix.go
new
+227
@@ -0,0 +1,227 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "sync"
7
+ "sync/atomic"
8
+
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
10
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
11
+)
12
+
13
+// Server is an internal managed server bound to one expected request kind.
14
+// Supports multiple concurrent client sessions up to workerCount.
15
+type Server struct {
16
+ runDir string
17
+ serviceName string
18
+ config posix.ServerConfig
19
+ expectedMethodCode uint16
20
+ handler DispatchHandler
21
+ running atomic.Bool
22
+ learnedRequestPayloadBytes atomic.Uint32
23
+ learnedResponsePayloadBytes atomic.Uint32
24
+ nextSessionID atomic.Uint64
25
+ workerCount int
26
+ wg sync.WaitGroup
27
+}
28
+
29
+// NewServer creates a new managed server. workerCount limits the
30
+// maximum number of concurrent client sessions (default 1 if <= 0).
31
+func NewServer(
32
+ runDir, serviceName string,
33
+ config posix.ServerConfig,
34
+ expectedMethodCode uint16,
35
+ handler DispatchHandler,
36
+) *Server {
37
+ return NewServerWithWorkers(runDir, serviceName, config, expectedMethodCode, handler, defaultServerWorkerCount)
38
+}
39
+
40
+// NewServerWithWorkers creates a server with an explicit worker count limit.
41
+func NewServerWithWorkers(
42
+ runDir, serviceName string,
43
+ config posix.ServerConfig,
44
+ expectedMethodCode uint16,
45
+ handler DispatchHandler,
46
+ workerCount int,
47
+) *Server {
48
+ s := &Server{config: config}
49
+ s.initCommon(
50
+ runDir,
51
+ serviceName,
52
+ expectedMethodCode,
53
+ handler,
54
+ workerCount,
55
+ config.MaxRequestPayloadBytes,
56
+ config.MaxResponsePayloadBytes,
57
+ )
58
+ return s
59
+}
60
+
61
+const posixShmProfiles = protocol.ProfileSHMHybrid | protocol.ProfileSHMFutex
62
+
63
+func (s *Server) prepareAcceptConfig() (uint64, posix.ServerConfig, *posix.ShmContext, bool) {
64
+ sessionID := s.nextSessionID.Add(1)
65
+ cfg := s.config
66
+ cfg.MaxRequestPayloadBytes = s.learnedRequestPayloadBytes.Load()
67
+ cfg.MaxResponsePayloadBytes = s.learnedResponsePayloadBytes.Load()
68
+
69
+ if cfg.SupportedProfiles&posixShmProfiles == 0 {
70
+ return sessionID, cfg, nil, true
71
+ }
72
+
73
+ shm, err := posix.ShmServerCreate(
74
+ s.runDir, s.serviceName, sessionID,
75
+ cfg.MaxRequestPayloadBytes+uint32(protocol.HeaderSize),
76
+ cfg.MaxResponsePayloadBytes+uint32(protocol.HeaderSize),
77
+ )
78
+ if err == nil {
79
+ return sessionID, cfg, shm, true
80
+ }
81
+
82
+ cfg.SupportedProfiles &^= posixShmProfiles
83
+ cfg.PreferredProfiles &^= posixShmProfiles
84
+ if cfg.SupportedProfiles == 0 {
85
+ return sessionID, cfg, nil, false
86
+ }
87
+
88
+ return sessionID, cfg, nil, true
89
+}
90
+
91
+// Run starts the acceptor loop. Blocking. Accepts clients, spawns a
92
+// goroutine per session (up to workerCount concurrently).
93
+// Returns when Stop() is called or on fatal error.
94
+func (s *Server) Run() error {
95
+ posix.ShmCleanupStale(s.runDir, s.serviceName)
96
+
97
+ listener, err := posix.Listen(s.runDir, s.serviceName, s.config)
98
+ if err != nil {
99
+ return err
100
+ }
101
+ defer listener.Close()
102
+
103
+ s.running.Store(true)
104
+
105
+ /* Semaphore channel limits concurrent sessions */
106
+ sem := make(chan struct{}, s.workerCount)
107
+
108
+ for s.running.Load() {
109
+ // Poll the listener fd before blocking on accept
110
+ ready := pollFd(listener.Fd(), serverPollTimeoutMs)
111
+ if ready < 0 {
112
+ break
113
+ }
114
+ if ready == 0 {
115
+ continue
116
+ }
117
+
118
+ sessionID, acceptCfg, precreatedShm, ok := s.prepareAcceptConfig()
119
+ if !ok {
120
+ s.retryAcceptAfter(nil)
121
+ continue
122
+ }
123
+
124
+ session, err := listener.AcceptWithConfig(sessionID, acceptCfg)
125
+ if err != nil {
126
+ if !s.retryAcceptAfter(func() {
127
+ if precreatedShm != nil {
128
+ precreatedShm.ShmDestroy()
129
+ }
130
+ }) {
131
+ break
132
+ }
133
+ continue
134
+ }
135
+
136
+ if !s.acquireWorkerSlot(sem, func() {
137
+ if precreatedShm != nil {
138
+ precreatedShm.ShmDestroy()
139
+ }
140
+ }, session.Close) {
141
+ continue
142
+ }
143
+
144
+ var shm *posix.ShmContext
145
+ if session.SelectedProfile == protocol.ProfileSHMHybrid ||
146
+ session.SelectedProfile == protocol.ProfileSHMFutex {
147
+ if precreatedShm == nil {
148
+ session.Close()
149
+ <-sem
150
+ continue
151
+ }
152
+ shm = precreatedShm
153
+ } else if precreatedShm != nil {
154
+ precreatedShm.ShmDestroy()
155
+ }
156
+
157
+ s.startSessionWorker(sem, func() {
158
+ s.handleSession(session, shm)
159
+ })
160
+ }
161
+
162
+ // Wait for all active session goroutines to finish
163
+ s.wg.Wait()
164
+
165
+ return nil
166
+}
167
+
168
+// Stop signals the server to stop.
169
+func (s *Server) Stop() {
170
+ s.running.Store(false)
171
+}
172
+
173
+func (s *Server) handleSession(session *posix.Session, shm *posix.ShmContext) {
174
+ s.handleServerSession(serverSessionOps{
175
+ maxRequestPayloadBytes: session.MaxRequestPayloadBytes,
176
+ maxResponsePayloadBytes: session.MaxResponsePayloadBytes,
177
+ receive: func(recvBuf []byte) (protocol.Header, []byte, serverReceiveAction) {
178
+ if shm != nil {
179
+ mlen, err := shm.ShmReceive(recvBuf, serverPollTimeoutMs)
180
+ if err != nil {
181
+ if err == posix.ErrShmTimeout {
182
+ return protocol.Header{}, nil, serverReceiveContinue
183
+ }
184
+ return protocol.Header{}, nil, serverReceiveStop
185
+ }
186
+ if mlen < protocol.HeaderSize {
187
+ return protocol.Header{}, nil, serverReceiveStop
188
+ }
189
+ hdr, err := protocol.DecodeHeader(recvBuf[:mlen])
190
+ if err != nil {
191
+ return protocol.Header{}, nil, serverReceiveStop
192
+ }
193
+ return hdr, recvBuf[protocol.HeaderSize:mlen], serverReceiveOK
194
+ }
195
+
196
+ ready := pollFd(session.Fd(), serverPollTimeoutMs)
197
+ if ready < 0 {
198
+ return protocol.Header{}, nil, serverReceiveStop
199
+ }
200
+ if ready == 0 {
201
+ return protocol.Header{}, nil, serverReceiveContinue
202
+ }
203
+
204
+ hdr, payload, err := session.Receive(recvBuf)
205
+ if err != nil {
206
+ return protocol.Header{}, nil, serverReceiveStop
207
+ }
208
+ return hdr, payload, serverReceiveOK
209
+ },
210
+ send: func(respHdr *protocol.Header, payload []byte, msgBuf *[]byte) error {
211
+ if shm == nil {
212
+ return session.Send(respHdr, payload)
213
+ }
214
+ msg, err := serverEncodeSharedResponse(respHdr, payload, msgBuf)
215
+ if err != nil {
216
+ return err
217
+ }
218
+ return shm.ShmSend(msg)
219
+ },
220
+ close: func() {
221
+ if shm != nil {
222
+ shm.ShmDestroy()
223
+ }
224
+ session.Close()
225
+ },
226
+ })
227
+}
src/go/pkg/netipc/service/raw/server_windows.go
new
+276
@@ -0,0 +1,276 @@
1
+//go:build windows
2
+
3
+package raw
4
+
5
+import (
6
+ "sync"
7
+ "sync/atomic"
8
+
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
10
+ windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
11
+)
12
+
13
+// Server is an internal managed server bound to one expected request kind.
14
+type Server struct {
15
+ runDir string
16
+ serviceName string
17
+ config windows.ServerConfig
18
+ expectedMethodCode uint16
19
+ handler DispatchHandler
20
+ running atomic.Bool
21
+ learnedRequestPayloadBytes atomic.Uint32
22
+ learnedResponsePayloadBytes atomic.Uint32
23
+ nextSessionID atomic.Uint64
24
+ workerCount int
25
+ wg sync.WaitGroup
26
+ listener *windows.Listener // stored so Stop() can close it
27
+}
28
+
29
+// NewServer creates a new managed server.
30
+func NewServer(
31
+ runDir, serviceName string,
32
+ config windows.ServerConfig,
33
+ expectedMethodCode uint16,
34
+ handler DispatchHandler,
35
+) *Server {
36
+ return NewServerWithWorkers(runDir, serviceName, config, expectedMethodCode, handler, defaultServerWorkerCount)
37
+}
38
+
39
+// NewServerWithWorkers creates a server with an explicit worker count limit.
40
+func NewServerWithWorkers(
41
+ runDir, serviceName string,
42
+ config windows.ServerConfig,
43
+ expectedMethodCode uint16,
44
+ handler DispatchHandler,
45
+ workerCount int,
46
+) *Server {
47
+ s := &Server{config: config}
48
+ s.initCommon(
49
+ runDir,
50
+ serviceName,
51
+ expectedMethodCode,
52
+ handler,
53
+ workerCount,
54
+ config.MaxRequestPayloadBytes,
55
+ config.MaxResponsePayloadBytes,
56
+ )
57
+ return s
58
+}
59
+
60
+type preparedWinShm struct {
61
+ hybrid *windows.WinShmContext
62
+ busywait *windows.WinShmContext
63
+}
64
+
65
+func (p *preparedWinShm) take(profile uint32) *windows.WinShmContext {
66
+ if p == nil {
67
+ return nil
68
+ }
69
+ switch profile {
70
+ case windows.WinShmProfileHybrid:
71
+ ctx := p.hybrid
72
+ p.hybrid = nil
73
+ return ctx
74
+ case windows.WinShmProfileBusywait:
75
+ ctx := p.busywait
76
+ p.busywait = nil
77
+ return ctx
78
+ default:
79
+ return nil
80
+ }
81
+}
82
+
83
+func (p *preparedWinShm) destroyAll() {
84
+ if p == nil {
85
+ return
86
+ }
87
+ if p.hybrid != nil {
88
+ p.hybrid.WinShmDestroy()
89
+ p.hybrid = nil
90
+ }
91
+ if p.busywait != nil {
92
+ p.busywait.WinShmDestroy()
93
+ p.busywait = nil
94
+ }
95
+}
96
+
97
+const winShmProfiles = windows.WinShmProfileHybrid | windows.WinShmProfileBusywait
98
+
99
+func (s *Server) prepareAcceptConfig() (uint64, windows.ServerConfig, *preparedWinShm, bool) {
100
+ sessionID := s.nextSessionID.Add(1)
101
+ cfg := s.config
102
+ cfg.MaxRequestPayloadBytes = s.learnedRequestPayloadBytes.Load()
103
+ cfg.MaxResponsePayloadBytes = s.learnedResponsePayloadBytes.Load()
104
+
105
+ if cfg.SupportedProfiles&winShmProfiles == 0 {
106
+ return sessionID, cfg, nil, true
107
+ }
108
+
109
+ prepared := &preparedWinShm{}
110
+ for _, profile := range []uint32{windows.WinShmProfileHybrid, windows.WinShmProfileBusywait} {
111
+ if cfg.SupportedProfiles&profile == 0 {
112
+ continue
113
+ }
114
+ shm, err := windows.WinShmServerCreate(
115
+ s.runDir, s.serviceName,
116
+ cfg.AuthToken,
117
+ sessionID,
118
+ profile,
119
+ cfg.MaxRequestPayloadBytes+uint32(protocol.HeaderSize),
120
+ cfg.MaxResponsePayloadBytes+uint32(protocol.HeaderSize),
121
+ )
122
+ if err != nil {
123
+ cfg.SupportedProfiles &^= profile
124
+ cfg.PreferredProfiles &^= profile
125
+ continue
126
+ }
127
+ if profile == windows.WinShmProfileHybrid {
128
+ prepared.hybrid = shm
129
+ } else {
130
+ prepared.busywait = shm
131
+ }
132
+ }
133
+
134
+ if cfg.SupportedProfiles == 0 {
135
+ prepared.destroyAll()
136
+ return sessionID, cfg, nil, false
137
+ }
138
+
139
+ if prepared.hybrid == nil && prepared.busywait == nil {
140
+ return sessionID, cfg, nil, true
141
+ }
142
+
143
+ return sessionID, cfg, prepared, true
144
+}
145
+
146
+// Run starts the acceptor loop. Blocking.
147
+func (s *Server) Run() error {
148
+ listener, err := windows.Listen(s.runDir, s.serviceName, s.config)
149
+ if err != nil {
150
+ return err
151
+ }
152
+ s.listener = listener
153
+ defer func() {
154
+ listener.Close()
155
+ s.listener = nil
156
+ }()
157
+
158
+ s.running.Store(true)
159
+ sem := make(chan struct{}, s.workerCount)
160
+
161
+ for s.running.Load() {
162
+ sessionID, acceptCfg, preparedShm, ok := s.prepareAcceptConfig()
163
+ if !ok {
164
+ s.retryAcceptAfter(nil)
165
+ continue
166
+ }
167
+
168
+ session, err := listener.AcceptWithConfig(sessionID, acceptCfg)
169
+ if err != nil {
170
+ if !s.retryAcceptAfter(func() {
171
+ if preparedShm != nil {
172
+ preparedShm.destroyAll()
173
+ }
174
+ }) {
175
+ break
176
+ }
177
+ continue
178
+ }
179
+
180
+ if !s.acquireWorkerSlot(sem, func() {
181
+ if preparedShm != nil {
182
+ preparedShm.destroyAll()
183
+ }
184
+ }, session.Close) {
185
+ continue
186
+ }
187
+
188
+ var shm *windows.WinShmContext
189
+ if session.SelectedProfile == windows.WinShmProfileHybrid ||
190
+ session.SelectedProfile == windows.WinShmProfileBusywait {
191
+ shm = preparedShm.take(session.SelectedProfile)
192
+ if shm == nil {
193
+ if preparedShm != nil {
194
+ preparedShm.destroyAll()
195
+ }
196
+ session.Close()
197
+ <-sem
198
+ continue
199
+ }
200
+ }
201
+ if preparedShm != nil {
202
+ preparedShm.destroyAll()
203
+ }
204
+
205
+ s.startSessionWorker(sem, func() {
206
+ s.handleSession(session, shm)
207
+ })
208
+ }
209
+
210
+ s.wg.Wait()
211
+ return nil
212
+}
213
+
214
+// Stop signals the server to stop and unblocks Accept by closing the listener.
215
+func (s *Server) Stop() {
216
+ s.running.Store(false)
217
+ if s.listener != nil {
218
+ s.listener.Close()
219
+ }
220
+}
221
+
222
+func (s *Server) handleSession(session *windows.Session, shm *windows.WinShmContext) {
223
+ s.handleServerSession(serverSessionOps{
224
+ maxRequestPayloadBytes: session.MaxRequestPayloadBytes,
225
+ maxResponsePayloadBytes: session.MaxResponsePayloadBytes,
226
+ receive: func(recvBuf []byte) (protocol.Header, []byte, serverReceiveAction) {
227
+ if shm != nil {
228
+ mlen, err := shm.WinShmReceive(recvBuf, serverPollTimeoutMs)
229
+ if err != nil {
230
+ if err == windows.ErrWinShmTimeout {
231
+ return protocol.Header{}, nil, serverReceiveContinue
232
+ }
233
+ return protocol.Header{}, nil, serverReceiveStop
234
+ }
235
+ if mlen < protocol.HeaderSize {
236
+ return protocol.Header{}, nil, serverReceiveStop
237
+ }
238
+ hdr, err := protocol.DecodeHeader(recvBuf[:mlen])
239
+ if err != nil {
240
+ return protocol.Header{}, nil, serverReceiveStop
241
+ }
242
+ return hdr, recvBuf[protocol.HeaderSize:mlen], serverReceiveOK
243
+ }
244
+
245
+ ready, waitErr := session.WaitReadable(serverPollTimeoutMs)
246
+ if waitErr != nil {
247
+ return protocol.Header{}, nil, serverReceiveStop
248
+ }
249
+ if !ready {
250
+ return protocol.Header{}, nil, serverReceiveContinue
251
+ }
252
+
253
+ hdr, payload, err := session.Receive(recvBuf)
254
+ if err != nil {
255
+ return protocol.Header{}, nil, serverReceiveStop
256
+ }
257
+ return hdr, payload, serverReceiveOK
258
+ },
259
+ send: func(respHdr *protocol.Header, payload []byte, msgBuf *[]byte) error {
260
+ if shm == nil {
261
+ return session.Send(respHdr, payload)
262
+ }
263
+ msg, err := serverEncodeSharedResponse(respHdr, payload, msgBuf)
264
+ if err != nil {
265
+ return err
266
+ }
267
+ return shm.WinShmSend(msg)
268
+ },
269
+ close: func() {
270
+ if shm != nil {
271
+ shm.WinShmDestroy()
272
+ }
273
+ session.Close()
274
+ },
275
+ })
276
+}
src/go/pkg/netipc/service/raw/string_reverse.go
new
+61
@@ -0,0 +1,61 @@
1
+package raw
2
+
3
+import "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
4
+
5
+// StringReverseHandler serves a single STRING_REVERSE service kind.
6
+type StringReverseHandler func(string) (string, bool)
7
+
8
+// CallStringReverse performs a blocking STRING_REVERSE call.
9
+// The returned view is valid until the next typed call on this client.
10
+func (c *Client) CallStringReverse(requestStr string) (*protocol.StringReverseView, error) {
11
+ if err := c.validateMethod(protocol.MethodStringReverse); err != nil {
12
+ return nil, err
13
+ }
14
+
15
+ var result *protocol.StringReverseView
16
+
17
+ err := c.callWithRetry(func() error {
18
+ reqBuf := ensureClientScratch(&c.requestBuf, protocol.StringReverseHdrSize+len(requestStr)+1)
19
+ if protocol.StringReverseEncode(requestStr, reqBuf) == 0 {
20
+ return protocol.ErrTruncated
21
+ }
22
+
23
+ _, payload, rerr := c.doRawCall(protocol.MethodStringReverse, reqBuf)
24
+ if rerr != nil {
25
+ return rerr
26
+ }
27
+
28
+ view, derr := protocol.StringReverseDecode(payload)
29
+ if derr != nil {
30
+ return derr
31
+ }
32
+ result = &view
33
+ return nil
34
+ })
35
+ if err != nil {
36
+ return nil, err
37
+ }
38
+ return result, nil
39
+}
40
+
41
+// StringReverseDispatch adapts a typed string-reverse handler to the raw dispatch shape.
42
+func StringReverseDispatch(handle StringReverseHandler) DispatchHandler {
43
+ if handle == nil {
44
+ return nil
45
+ }
46
+ return func(request []byte, responseBuf []byte) (int, error) {
47
+ view, err := protocol.StringReverseDecode(request)
48
+ if err != nil {
49
+ return 0, err
50
+ }
51
+ result, ok := handle(view.Str)
52
+ if !ok {
53
+ return 0, errHandlerFailed
54
+ }
55
+ n := protocol.StringReverseEncode(result, responseBuf)
56
+ if n == 0 {
57
+ return 0, protocol.ErrOverflow
58
+ }
59
+ return n, nil
60
+ }
61
+}
src/go/pkg/netipc/service/raw/string_reverse_unix.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build unix
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/posix"
8
+)
9
+
10
+// NewStringReverseClient creates a raw client bound to the string-reverse service kind.
11
+func NewStringReverseClient(runDir, serviceName string, config posix.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodStringReverse)
13
+}
src/go/pkg/netipc/service/raw/string_reverse_windows.go
new
+13
@@ -0,0 +1,13 @@
1
+//go:build windows
2
+
3
+package raw
4
+
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+ windows "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/windows"
8
+)
9
+
10
+// NewStringReverseClient creates a raw client bound to the string-reverse service kind.
11
+func NewStringReverseClient(runDir, serviceName string, config windows.ClientConfig) *Client {
12
+ return newClient(runDir, serviceName, config, protocol.MethodStringReverse)
13
+}
src/go/pkg/netipc/service/raw/types.go
-132
@@ -8,12 +8,6 @@
8
// Pure Go — no cgo. Works with CGO_ENABLED=0.
9
package raw
10
11
-import (
12
- "errors"
13
-
14
- "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
15
-)
16
-
11
// Poll/receive timeout for server loops (ms). Controls shutdown detection latency.
12
const serverPollTimeoutMs = 100
13
@@ -42,129 +36,3 @@ type ClientStatus struct {
36
CallCount uint32
37
ErrorCount uint32
38
}
45
-
46
-// IncrementHandler serves a single INCREMENT service kind.
47
-type IncrementHandler func(uint64) (uint64, bool)
48
-
49
-// StringReverseHandler serves a single STRING_REVERSE service kind.
50
-type StringReverseHandler func(string) (string, bool)
51
-
52
-// SnapshotHandler serves a single CGROUPS_SNAPSHOT service kind.
53
-type SnapshotHandler func(*protocol.CgroupsRequest, *protocol.CgroupsBuilder) bool
54
-
55
-var errHandlerFailed = errors.New("dispatch handler failed")
56
-
57
-// DispatchHandler validates/decodes a single service kind request and writes
58
-// the matching response into responseBuf.
59
-type DispatchHandler func(request []byte, responseBuf []byte) (int, error)
60
-
61
-// IncrementDispatch adapts a typed increment handler to the raw dispatch shape.
62
-func IncrementDispatch(handle IncrementHandler) DispatchHandler {
63
- if handle == nil {
64
- return nil
65
- }
66
- return func(request []byte, responseBuf []byte) (int, error) {
67
- value, err := protocol.IncrementDecode(request)
68
- if err != nil {
69
- return 0, err
70
- }
71
- result, ok := handle(value)
72
- if !ok {
73
- return 0, errHandlerFailed
74
- }
75
- n := protocol.IncrementEncode(result, responseBuf)
76
- if n == 0 {
77
- return 0, protocol.ErrOverflow
78
- }
79
- return n, nil
80
- }
81
-}
82
-
83
-// StringReverseDispatch adapts a typed string-reverse handler to the raw dispatch shape.
84
-func StringReverseDispatch(handle StringReverseHandler) DispatchHandler {
85
- if handle == nil {
86
- return nil
87
- }
88
- return func(request []byte, responseBuf []byte) (int, error) {
89
- view, err := protocol.StringReverseDecode(request)
90
- if err != nil {
91
- return 0, err
92
- }
93
- result, ok := handle(view.Str)
94
- if !ok {
95
- return 0, errHandlerFailed
96
- }
97
- n := protocol.StringReverseEncode(result, responseBuf)
98
- if n == 0 {
99
- return 0, protocol.ErrOverflow
100
- }
101
- return n, nil
102
- }
103
-}
104
-
105
-// SnapshotMaxItems returns the item budget for a single snapshot service kind.
106
-func SnapshotMaxItems(responseBufSize int, override uint32) uint32 {
107
- if override != 0 {
108
- return override
109
- }
110
- return protocol.EstimateCgroupsMaxItems(responseBufSize)
111
-}
112
-
113
-// SnapshotDispatch adapts a typed snapshot handler to the raw dispatch shape.
114
-func SnapshotDispatch(handle SnapshotHandler, maxItems uint32) DispatchHandler {
115
- if handle == nil {
116
- return nil
117
- }
118
- return func(request []byte, responseBuf []byte) (int, error) {
119
- req, err := protocol.DecodeCgroupsRequest(request)
120
- if err != nil {
121
- return 0, err
122
- }
123
- itemBudget := SnapshotMaxItems(len(responseBuf), maxItems)
124
- if itemBudget == 0 {
125
- return 0, protocol.ErrOverflow
126
- }
127
- minRequired, ok := protocol.CgroupsBuilderMinBytes(itemBudget)
128
- if !ok || len(responseBuf) < minRequired {
129
- return 0, protocol.ErrOverflow
130
- }
131
- builder := protocol.NewCgroupsBuilder(responseBuf, itemBudget, 0, 0)
132
- if !handle(&req, builder) {
133
- return 0, errHandlerFailed
134
- }
135
- n := builder.Finish()
136
- if n == 0 {
137
- return 0, protocol.ErrOverflow
138
- }
139
- return n, nil
140
- }
141
-}
142
-
143
-// ---------------------------------------------------------------------------
144
-// L3 cache types (shared across platforms)
145
-// ---------------------------------------------------------------------------
146
-
147
-// Default response buffer size for L3 cache refresh.
148
-const cacheResponseBufSize = 65536
149
-
150
-// CacheItem is an owned copy of a single cgroup item.
151
-// Built from ephemeral L2 views during cache construction.
152
-type CacheItem struct {
153
- Hash uint32
154
- Options uint32
155
- Enabled uint32
156
- Name string // owned copy
157
- Path string // owned copy
158
-}
159
-
160
-// CacheStatus is a diagnostic snapshot for the L3 cache.
161
-type CacheStatus struct {
162
- Populated bool
163
- ItemCount uint32
164
- SystemdEnabled uint32
165
- Generation uint64
166
- RefreshSuccessCount uint32
167
- RefreshFailureCount uint32
168
- ConnectionState ClientState // underlying L2 client state
169
- LastRefreshTs int64 // monotonic timestamp (ms) of last successful refresh, 0 if never
170
-}
src/go/pkg/netipc/transport/internal/framing/handshake.go
new
+312
@@ -0,0 +1,312 @@
1
+package framing
2
+
3
+import (
4
+ "encoding/binary"
5
+ "errors"
6
+
7
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
8
+)
9
+
10
+const (
11
+ HelloPayloadSize = 44
12
+ HelloAckPayloadSize = 48
13
+)
14
+
15
+type HelloConfig struct {
16
+ PacketSize uint32
17
+ SupportedProfiles uint32
18
+ PreferredProfiles uint32
19
+ MaxRequestPayloadBytes uint32
20
+ MaxRequestBatchItems uint32
21
+ MaxResponsePayloadBytes uint32
22
+ MaxResponseBatchItems uint32
23
+ AuthToken uint64
24
+}
25
+
26
+type ServerHelloConfig struct {
27
+ PacketSize uint32
28
+ MaxResponsePayloadBytes uint32
29
+ SupportedProfiles uint32
30
+ PreferredProfiles uint32
31
+ AuthToken uint64
32
+}
33
+
34
+type ClientHandshakeConfig struct {
35
+ Hello HelloConfig
36
+
37
+ Send func([]byte) error
38
+ Recv func([]byte) (int, error)
39
+ StatusError func(uint16) error
40
+
41
+ ErrSend func(string) error
42
+ ErrRecv func(string) error
43
+ ErrProtocol func(string) error
44
+ ErrIncompatible func(string) error
45
+}
46
+
47
+type ServerHandshakeConfig struct {
48
+ ServerHelloConfig
49
+ SessionID uint64
50
+
51
+ Recv func([]byte) (int, error)
52
+ SendAck func(uint16, protocol.HelloAck) error
53
+ StatusError func(uint16) error
54
+
55
+ ErrRecv func(string) error
56
+ ErrSend func(string) error
57
+ ErrProtocol func(string) error
58
+ ErrIncompatible func(string) error
59
+}
60
+
61
+func HeaderVersionIncompatible(buf []byte, expectedCode uint16) bool {
62
+ if len(buf) < protocol.HeaderSize {
63
+ return false
64
+ }
65
+
66
+ return binary.NativeEndian.Uint32(buf[0:4]) == protocol.MagicMsg &&
67
+ binary.NativeEndian.Uint16(buf[4:6]) != protocol.Version &&
68
+ binary.NativeEndian.Uint16(buf[6:8]) == protocol.HeaderLen &&
69
+ binary.NativeEndian.Uint16(buf[8:10]) == protocol.KindControl &&
70
+ binary.NativeEndian.Uint16(buf[12:14]) == expectedCode
71
+}
72
+
73
+func HelloLayoutIncompatible(buf []byte) bool {
74
+ return len(buf) >= 2 && binary.NativeEndian.Uint16(buf[0:2]) != 1
75
+}
76
+
77
+func HelloAckLayoutIncompatible(buf []byte) bool {
78
+ return len(buf) >= 2 && binary.NativeEndian.Uint16(buf[0:2]) != 1
79
+}
80
+
81
+func BuildHelloPacket(config HelloConfig) [protocol.HeaderSize + HelloPayloadSize]byte {
82
+ if config.SupportedProfiles == 0 {
83
+ config.SupportedProfiles = protocol.ProfileBaseline
84
+ }
85
+
86
+ hello := protocol.Hello{
87
+ LayoutVersion: 1,
88
+ Flags: 0,
89
+ SupportedProfiles: config.SupportedProfiles,
90
+ PreferredProfiles: config.PreferredProfiles,
91
+ MaxRequestPayloadBytes: config.MaxRequestPayloadBytes,
92
+ MaxRequestBatchItems: config.MaxRequestBatchItems,
93
+ MaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
94
+ MaxResponseBatchItems: config.MaxResponseBatchItems,
95
+ AuthToken: config.AuthToken,
96
+ PacketSize: config.PacketSize,
97
+ }
98
+
99
+ var payload [HelloPayloadSize]byte
100
+ hello.Encode(payload[:])
101
+
102
+ hdr := protocol.Header{
103
+ Magic: protocol.MagicMsg,
104
+ Version: protocol.Version,
105
+ HeaderLen: protocol.HeaderLen,
106
+ Kind: protocol.KindControl,
107
+ Flags: 0,
108
+ Code: protocol.CodeHello,
109
+ TransportStatus: protocol.StatusOK,
110
+ PayloadLen: HelloPayloadSize,
111
+ ItemCount: 1,
112
+ MessageID: 0,
113
+ }
114
+
115
+ var pkt [protocol.HeaderSize + HelloPayloadSize]byte
116
+ hdr.Encode(pkt[:protocol.HeaderSize])
117
+ copy(pkt[protocol.HeaderSize:], payload[:])
118
+ return pkt
119
+}
120
+
121
+func DecodeControlHeader(buf []byte, expectedCode uint16) (protocol.Header, error) {
122
+ hdr, err := protocol.DecodeHeader(buf)
123
+ if err != nil {
124
+ return protocol.Header{}, err
125
+ }
126
+ if hdr.Kind != protocol.KindControl || hdr.Code != expectedCode {
127
+ return protocol.Header{}, protocol.ErrBadKind
128
+ }
129
+ return hdr, nil
130
+}
131
+
132
+func DecodeHelloPayload(buf []byte) (protocol.Hello, error) {
133
+ return protocol.DecodeHello(buf)
134
+}
135
+
136
+func DecodeHelloAckPayload(buf []byte) (protocol.HelloAck, error) {
137
+ return protocol.DecodeHelloAck(buf)
138
+}
139
+
140
+func ClientHandshake(config ClientHandshakeConfig) (protocol.HelloAck, error) {
141
+ pkt := BuildHelloPacket(config.Hello)
142
+ if err := config.Send(pkt[:]); err != nil {
143
+ return protocol.HelloAck{}, config.ErrSend("hello send: " + err.Error())
144
+ }
145
+
146
+ var ackBuf [128]byte
147
+ n, err := config.Recv(ackBuf[:])
148
+ if err != nil {
149
+ return protocol.HelloAck{}, config.ErrRecv("hello_ack recv: " + err.Error())
150
+ }
151
+
152
+ ackHdr, err := DecodeControlHeader(ackBuf[:n], protocol.CodeHelloAck)
153
+ if err != nil {
154
+ if errors.Is(err, protocol.ErrBadVersion) {
155
+ return protocol.HelloAck{}, config.ErrIncompatible("ack header version mismatch")
156
+ }
157
+ if errors.Is(err, protocol.ErrBadKind) {
158
+ return protocol.HelloAck{}, config.ErrProtocol("expected HELLO_ACK")
159
+ }
160
+ return protocol.HelloAck{}, config.ErrProtocol("ack header: " + err.Error())
161
+ }
162
+
163
+ if err := config.StatusError(ackHdr.TransportStatus); err != nil {
164
+ return protocol.HelloAck{}, err
165
+ }
166
+
167
+ if n < protocol.HeaderSize+HelloAckPayloadSize {
168
+ return protocol.HelloAck{}, config.ErrProtocol("ack payload truncated")
169
+ }
170
+ ack, err := DecodeHelloAckPayload(ackBuf[protocol.HeaderSize:n])
171
+ if err != nil {
172
+ if errors.Is(err, protocol.ErrBadLayout) &&
173
+ HelloAckLayoutIncompatible(ackBuf[protocol.HeaderSize:n]) {
174
+ return protocol.HelloAck{}, config.ErrIncompatible("ack payload layout version mismatch")
175
+ }
176
+ return protocol.HelloAck{}, config.ErrProtocol("ack payload: " + err.Error())
177
+ }
178
+
179
+ return ack, nil
180
+}
181
+
182
+func NegotiateHello(hello protocol.Hello, config ServerHelloConfig) (protocol.HelloAck, uint16, bool) {
183
+ if config.SupportedProfiles == 0 {
184
+ config.SupportedProfiles = protocol.ProfileBaseline
185
+ }
186
+
187
+ intersection := hello.SupportedProfiles & config.SupportedProfiles
188
+ if intersection == 0 {
189
+ return protocol.HelloAck{}, protocol.StatusUnsupported, false
190
+ }
191
+ if hello.AuthToken != config.AuthToken {
192
+ return protocol.HelloAck{}, protocol.StatusAuthFailed, false
193
+ }
194
+ // Level 1 keeps request payload limits client-proposed: the server rejects
195
+ // values over the wire cap and echoes accepted values unchanged.
196
+ if hello.MaxRequestPayloadBytes > protocol.MaxPayloadCap {
197
+ return protocol.HelloAck{}, protocol.StatusLimitExceeded, false
198
+ }
199
+
200
+ preferredIntersection := intersection & hello.PreferredProfiles & config.PreferredProfiles
201
+ selected := highestBit(intersection)
202
+ if preferredIntersection != 0 {
203
+ selected = highestBit(preferredIntersection)
204
+ }
205
+
206
+ agreedPkt := minU32(hello.PacketSize, config.PacketSize)
207
+ if agreedPkt <= protocol.HeaderSize {
208
+ return protocol.HelloAck{}, protocol.StatusIncompatible, false
209
+ }
210
+
211
+ ack := protocol.HelloAck{
212
+ LayoutVersion: 1,
213
+ Flags: 0,
214
+ ServerSupportedProfiles: config.SupportedProfiles,
215
+ IntersectionProfiles: intersection,
216
+ SelectedProfile: selected,
217
+ AgreedMaxRequestPayloadBytes: hello.MaxRequestPayloadBytes,
218
+ AgreedMaxRequestBatchItems: hello.MaxRequestBatchItems,
219
+ AgreedMaxResponsePayloadBytes: config.MaxResponsePayloadBytes,
220
+ AgreedMaxResponseBatchItems: hello.MaxRequestBatchItems,
221
+ AgreedPacketSize: agreedPkt,
222
+ }
223
+ return ack, protocol.StatusOK, true
224
+}
225
+
226
+func ServerHandshake(config ServerHandshakeConfig) (protocol.HelloAck, error) {
227
+ var buf [128]byte
228
+ n, err := config.Recv(buf[:])
229
+ if err != nil {
230
+ return protocol.HelloAck{}, config.ErrRecv("hello recv: " + err.Error())
231
+ }
232
+
233
+ _, err = DecodeControlHeader(buf[:n], protocol.CodeHello)
234
+ if err != nil {
235
+ if errors.Is(err, protocol.ErrBadVersion) &&
236
+ HeaderVersionIncompatible(buf[:n], protocol.CodeHello) {
237
+ sendRejection(config.SendAck, protocol.StatusIncompatible)
238
+ return protocol.HelloAck{}, config.ErrIncompatible("hello header version mismatch")
239
+ }
240
+ if errors.Is(err, protocol.ErrBadKind) {
241
+ return protocol.HelloAck{}, config.ErrProtocol("expected HELLO")
242
+ }
243
+ return protocol.HelloAck{}, config.ErrProtocol("hello header: " + err.Error())
244
+ }
245
+
246
+ hello, err := DecodeHelloPayload(buf[protocol.HeaderSize:n])
247
+ if err != nil {
248
+ if errors.Is(err, protocol.ErrBadLayout) &&
249
+ HelloLayoutIncompatible(buf[protocol.HeaderSize:n]) {
250
+ sendRejection(config.SendAck, protocol.StatusIncompatible)
251
+ return protocol.HelloAck{}, config.ErrIncompatible("hello payload layout version mismatch")
252
+ }
253
+ return protocol.HelloAck{}, config.ErrProtocol("hello payload: " + err.Error())
254
+ }
255
+
256
+ ack, status, ok := NegotiateHello(hello, config.ServerHelloConfig)
257
+ if !ok {
258
+ sendRejection(config.SendAck, status)
259
+ return protocol.HelloAck{}, config.StatusError(status)
260
+ }
261
+ ack.SessionID = config.SessionID
262
+
263
+ if err := config.SendAck(protocol.StatusOK, ack); err != nil {
264
+ return protocol.HelloAck{}, config.ErrSend("hello_ack send: " + err.Error())
265
+ }
266
+
267
+ return ack, nil
268
+}
269
+
270
+func BuildHelloAckPacket(status uint16, ack protocol.HelloAck) [protocol.HeaderSize + HelloAckPayloadSize]byte {
271
+ var payload [HelloAckPayloadSize]byte
272
+ ack.Encode(payload[:])
273
+
274
+ hdr := protocol.Header{
275
+ Magic: protocol.MagicMsg,
276
+ Version: protocol.Version,
277
+ HeaderLen: protocol.HeaderLen,
278
+ Kind: protocol.KindControl,
279
+ Code: protocol.CodeHelloAck,
280
+ TransportStatus: status,
281
+ PayloadLen: HelloAckPayloadSize,
282
+ ItemCount: 1,
283
+ }
284
+
285
+ var pkt [protocol.HeaderSize + HelloAckPayloadSize]byte
286
+ hdr.Encode(pkt[:protocol.HeaderSize])
287
+ copy(pkt[protocol.HeaderSize:], payload[:])
288
+ return pkt
289
+}
290
+
291
+func sendRejection(sendAck func(uint16, protocol.HelloAck) error, status uint16) {
292
+ _ = sendAck(status, protocol.HelloAck{LayoutVersion: 1})
293
+}
294
+
295
+func highestBit(mask uint32) uint32 {
296
+ if mask == 0 {
297
+ return 0
298
+ }
299
+ var b uint32 = 1
300
+ for mask>>1 != 0 {
301
+ mask >>= 1
302
+ b <<= 1
303
+ }
304
+ return b
305
+}
306
+
307
+func minU32(a, b uint32) uint32 {
308
+ if a < b {
309
+ return a
310
+ }
311
+ return b
312
+}
src/go/pkg/netipc/transport/internal/framing/receive.go
new
+300
@@ -0,0 +1,300 @@
1
+package framing
2
+
3
+import (
4
+ "fmt"
5
+
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+)
8
+
9
+// Receiver assembles one framed NetIPC message over a transport-specific
10
+// packet receive function.
11
+type Receiver struct {
12
+ PacketSize uint32
13
+
14
+ MaxPayload uint32
15
+ MaxBatchItems uint32
16
+
17
+ TrackResponses bool
18
+ InflightIDs map[uint64]struct{}
19
+
20
+ RecvBuf *[]byte
21
+ PacketBuf *[]byte
22
+
23
+ Recv func([]byte) (int, error)
24
+ EnsurePacketScratch func(*[]byte, int) []byte
25
+ OnRecvError func(error)
26
+
27
+ ErrLimitExceeded func(string) error
28
+ ErrProtocol func(string) error
29
+ ErrChunk func(string) error
30
+ ErrUnknownMsgID func(string) error
31
+ ErrRecv func(string) error
32
+}
33
+
34
+type SessionReceiveConfig struct {
35
+ RoleServer bool
36
+
37
+ PacketSize uint32
38
+ MaxRequestPayloadBytes uint32
39
+ MaxRequestBatchItems uint32
40
+ MaxResponsePayloadBytes uint32
41
+ MaxResponseBatchItems uint32
42
+
43
+ InflightIDs map[uint64]struct{}
44
+ RecvBuf *[]byte
45
+ PacketBuf *[]byte
46
+
47
+ Recv func([]byte) (int, error)
48
+ EnsurePacketScratch func(*[]byte, int) []byte
49
+ IsRecvDisconnect func(error) bool
50
+ FailAllInflight func()
51
+
52
+ ErrLimitExceeded func(string) error
53
+ ErrProtocol func(string) error
54
+ ErrChunk func(string) error
55
+ ErrUnknownMsgID func(string) error
56
+ ErrRecv func(string) error
57
+}
58
+
59
+func SessionReceive(config SessionReceiveConfig, buf []byte) (protocol.Header, []byte, error) {
60
+ maxPayload := config.MaxResponsePayloadBytes
61
+ maxBatch := config.MaxResponseBatchItems
62
+ if config.RoleServer {
63
+ maxPayload = config.MaxRequestPayloadBytes
64
+ maxBatch = config.MaxRequestBatchItems
65
+ }
66
+
67
+ return Receiver{
68
+ PacketSize: config.PacketSize,
69
+ MaxPayload: maxPayload,
70
+ MaxBatchItems: maxBatch,
71
+ TrackResponses: !config.RoleServer,
72
+ InflightIDs: config.InflightIDs,
73
+ RecvBuf: config.RecvBuf,
74
+ PacketBuf: config.PacketBuf,
75
+ Recv: config.Recv,
76
+ EnsurePacketScratch: config.EnsurePacketScratch,
77
+ OnRecvError: func(err error) {
78
+ if config.IsRecvDisconnect(err) {
79
+ config.FailAllInflight()
80
+ }
81
+ },
82
+ ErrLimitExceeded: config.ErrLimitExceeded,
83
+ ErrProtocol: config.ErrProtocol,
84
+ ErrChunk: config.ErrChunk,
85
+ ErrUnknownMsgID: config.ErrUnknownMsgID,
86
+ ErrRecv: config.ErrRecv,
87
+ }.Receive(buf)
88
+}
89
+
90
+// Receive reads and validates one complete logical message.
91
+func (r Receiver) Receive(buf []byte) (protocol.Header, []byte, error) {
92
+ n, err := r.Recv(buf)
93
+ if err != nil {
94
+ r.noteRecvError(err)
95
+ return protocol.Header{}, nil, err
96
+ }
97
+
98
+ if n < protocol.HeaderSize {
99
+ return protocol.Header{}, nil, r.ErrProtocol("packet too short for header")
100
+ }
101
+
102
+ hdr, err := protocol.DecodeHeader(buf[:n])
103
+ if err != nil {
104
+ return protocol.Header{}, nil, r.ErrProtocol("header decode: " + err.Error())
105
+ }
106
+
107
+ if err := r.validateInboundLimits(hdr); err != nil {
108
+ return protocol.Header{}, nil, err
109
+ }
110
+ if err := r.trackInboundResponse(hdr); err != nil {
111
+ return protocol.Header{}, nil, err
112
+ }
113
+
114
+ totalMsg, err := r.totalMessageLen(hdr)
115
+ if err != nil {
116
+ return protocol.Header{}, nil, err
117
+ }
118
+ payloadLen := int(hdr.PayloadLen)
119
+ if n > totalMsg {
120
+ return protocol.Header{}, nil, r.ErrProtocol("packet exceeds declared payload_len")
121
+ }
122
+ if n == totalMsg {
123
+ payload := buf[protocol.HeaderSize : protocol.HeaderSize+payloadLen]
124
+ if err := r.validateBatchPayload(hdr, payload); err != nil {
125
+ return protocol.Header{}, nil, err
126
+ }
127
+ return hdr, payload, nil
128
+ }
129
+
130
+ return r.receiveChunked(buf, n, hdr, totalMsg)
131
+}
132
+
133
+func (r Receiver) totalMessageLen(hdr protocol.Header) (int, error) {
134
+ maxInt := uint64(int(^uint(0) >> 1))
135
+ totalMsg := uint64(protocol.HeaderSize) + uint64(hdr.PayloadLen)
136
+ if totalMsg > maxInt {
137
+ return 0, r.ErrLimitExceeded("total message length exceeds platform limit")
138
+ }
139
+ return int(totalMsg), nil
140
+}
141
+
142
+func (r Receiver) noteRecvError(err error) {
143
+ if r.OnRecvError != nil {
144
+ r.OnRecvError(err)
145
+ }
146
+}
147
+
148
+func (r Receiver) validateInboundLimits(hdr protocol.Header) error {
149
+ if hdr.PayloadLen > r.MaxPayload {
150
+ return r.ErrLimitExceeded(
151
+ fmt.Sprintf("payload_len %d exceeds negotiated max %d", hdr.PayloadLen, r.MaxPayload))
152
+ }
153
+ if hdr.ItemCount > r.MaxBatchItems {
154
+ return r.ErrLimitExceeded(
155
+ fmt.Sprintf("item_count %d exceeds negotiated max %d", hdr.ItemCount, r.MaxBatchItems))
156
+ }
157
+ return nil
158
+}
159
+
160
+func (r Receiver) trackInboundResponse(hdr protocol.Header) error {
161
+ if !r.TrackResponses || hdr.Kind != protocol.KindResponse {
162
+ return nil
163
+ }
164
+ if r.InflightIDs == nil {
165
+ return r.ErrUnknownMsgID(fmt.Sprintf("message_id %d", hdr.MessageID))
166
+ }
167
+ if _, exists := r.InflightIDs[hdr.MessageID]; !exists {
168
+ return r.ErrUnknownMsgID(fmt.Sprintf("message_id %d", hdr.MessageID))
169
+ }
170
+ delete(r.InflightIDs, hdr.MessageID)
171
+ return nil
172
+}
173
+
174
+func (r Receiver) validateBatchPayload(hdr protocol.Header, payload []byte) error {
175
+ if hdr.Flags&protocol.FlagBatch == 0 || hdr.ItemCount <= 1 {
176
+ return nil
177
+ }
178
+
179
+ dirBytes64 := uint64(hdr.ItemCount) * 8
180
+ if dirBytes64 > uint64(int(^uint(0)>>1)) {
181
+ return r.ErrProtocol("batch dir exceeds platform limit")
182
+ }
183
+ dirBytes := int(dirBytes64)
184
+ dirAligned := protocol.Align8(dirBytes)
185
+ if len(payload) < dirAligned {
186
+ return r.ErrProtocol("batch dir exceeds payload")
187
+ }
188
+ packedAreaLen := uint32(len(payload) - dirAligned)
189
+ if err := protocol.BatchDirValidate(payload[:dirBytes], hdr.ItemCount, packedAreaLen); err != nil {
190
+ return r.ErrProtocol("batch dir: " + err.Error())
191
+ }
192
+ return nil
193
+}
194
+
195
+func (r Receiver) receiveChunked(
196
+ buf []byte,
197
+ firstPacketLen int,
198
+ hdr protocol.Header,
199
+ totalMsg int,
200
+) (protocol.Header, []byte, error) {
201
+ firstPayloadBytes := firstPacketLen - protocol.HeaderSize
202
+ needed := int(hdr.PayloadLen)
203
+ if len(*r.RecvBuf) < needed {
204
+ *r.RecvBuf = make([]byte, needed)
205
+ }
206
+
207
+ copy((*r.RecvBuf)[:firstPayloadBytes],
208
+ buf[protocol.HeaderSize:protocol.HeaderSize+firstPayloadBytes])
209
+
210
+ assembled := firstPayloadBytes
211
+ chunkPayloadBudget := int(r.PacketSize) - protocol.HeaderSize
212
+ expectedChunkCount := expectedReceiveChunkCount(
213
+ int(hdr.PayloadLen), firstPayloadBytes, chunkPayloadBudget)
214
+
215
+ pktBuf := r.EnsurePacketScratch(r.PacketBuf, int(r.PacketSize))
216
+ for ci := uint32(1); assembled < int(hdr.PayloadLen); ci++ {
217
+ err := r.receiveOneChunk(pktBuf, hdr, ci, expectedChunkCount,
218
+ totalMsg, &assembled)
219
+ if err != nil {
220
+ return protocol.Header{}, nil, err
221
+ }
222
+ }
223
+
224
+ payload := (*r.RecvBuf)[:int(hdr.PayloadLen)]
225
+ if err := r.validateBatchPayload(hdr, payload); err != nil {
226
+ return protocol.Header{}, nil, err
227
+ }
228
+ return hdr, payload, nil
229
+}
230
+
231
+func expectedReceiveChunkCount(payloadLen, firstPayloadBytes, chunkPayloadBudget int) uint32 {
232
+ remainingAfterFirst := payloadLen - firstPayloadBytes
233
+ expectedContinuations := uint32(0)
234
+ if remainingAfterFirst > 0 && chunkPayloadBudget > 0 {
235
+ expectedContinuations = uint32(1 + ((remainingAfterFirst - 1) / chunkPayloadBudget))
236
+ }
237
+ return 1 + expectedContinuations
238
+}
239
+
240
+func (r Receiver) receiveOneChunk(
241
+ pktBuf []byte,
242
+ hdr protocol.Header,
243
+ chunkIndex uint32,
244
+ expectedChunkCount uint32,
245
+ totalMsg int,
246
+ assembled *int,
247
+) error {
248
+ cn, err := r.Recv(pktBuf)
249
+ if err != nil {
250
+ r.noteRecvError(err)
251
+ return r.ErrRecv("continuation recv: " + err.Error())
252
+ }
253
+ if cn < protocol.HeaderSize {
254
+ return r.ErrChunk("continuation too short")
255
+ }
256
+
257
+ chk, err := protocol.DecodeChunkHeader(pktBuf[:cn])
258
+ if err != nil {
259
+ return r.ErrChunk("chunk header: " + err.Error())
260
+ }
261
+ if err := r.validateReceiveChunk(chk, hdr, chunkIndex, expectedChunkCount, totalMsg); err != nil {
262
+ return err
263
+ }
264
+
265
+ chunkData := cn - protocol.HeaderSize
266
+ if chunkData != int(chk.ChunkPayloadLen) {
267
+ return r.ErrChunk("chunk_payload_len mismatch")
268
+ }
269
+ if *assembled+chunkData > int(hdr.PayloadLen) {
270
+ return r.ErrChunk("chunk exceeds payload_len")
271
+ }
272
+
273
+ copy((*r.RecvBuf)[*assembled:*assembled+chunkData],
274
+ pktBuf[protocol.HeaderSize:protocol.HeaderSize+chunkData])
275
+ *assembled += chunkData
276
+ return nil
277
+}
278
+
279
+func (r Receiver) validateReceiveChunk(
280
+ chk protocol.ChunkHeader,
281
+ hdr protocol.Header,
282
+ chunkIndex uint32,
283
+ expectedChunkCount uint32,
284
+ totalMsg int,
285
+) error {
286
+ if chk.MessageID != hdr.MessageID {
287
+ return r.ErrChunk("message_id mismatch")
288
+ }
289
+ if chk.ChunkIndex != chunkIndex {
290
+ return r.ErrChunk(fmt.Sprintf(
291
+ "chunk_index mismatch: expected %d, got %d", chunkIndex, chk.ChunkIndex))
292
+ }
293
+ if chk.ChunkCount != expectedChunkCount {
294
+ return r.ErrChunk("chunk_count mismatch")
295
+ }
296
+ if chk.TotalMessageLen != uint32(totalMsg) {
297
+ return r.ErrChunk("total_message_len mismatch")
298
+ }
299
+ return nil
300
+}
src/go/pkg/netipc/transport/internal/framing/receive_test.go
new
+73
@@ -0,0 +1,73 @@
1
+package framing
2
+
3
+import (
4
+ "errors"
5
+ "strings"
6
+ "testing"
7
+
8
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
9
+)
10
+
11
+func TestReceiverRejectsPacketLongerThanDeclaredFrame(t *testing.T) {
12
+ packet := encodeReceiveTestPacket(1, []byte("ab"))
13
+ receiver := receiveTestReceiver(packet)
14
+
15
+ _, _, err := receiver.Receive(make([]byte, 64))
16
+ if err == nil {
17
+ t.Fatal("expected trailing packet bytes to be rejected")
18
+ }
19
+ if !strings.Contains(err.Error(), "exceeds declared payload_len") {
20
+ t.Fatalf("unexpected error: %v", err)
21
+ }
22
+}
23
+
24
+func TestReceiverAcceptsExactDeclaredFrame(t *testing.T) {
25
+ packet := encodeReceiveTestPacket(2, []byte("ab"))
26
+ receiver := receiveTestReceiver(packet)
27
+
28
+ hdr, payload, err := receiver.Receive(make([]byte, 64))
29
+ if err != nil {
30
+ t.Fatalf("Receive failed: %v", err)
31
+ }
32
+ if hdr.PayloadLen != 2 {
33
+ t.Fatalf("payload_len = %d, want 2", hdr.PayloadLen)
34
+ }
35
+ if string(payload) != "ab" {
36
+ t.Fatalf("payload = %q, want ab", payload)
37
+ }
38
+}
39
+
40
+func receiveTestReceiver(packet []byte) Receiver {
41
+ return Receiver{
42
+ PacketSize: 64,
43
+ MaxPayload: 64,
44
+ MaxBatchItems: 1,
45
+ Recv: func(buf []byte) (int, error) {
46
+ return copy(buf, packet), nil
47
+ },
48
+ ErrLimitExceeded: func(msg string) error { return errors.New(msg) },
49
+ ErrProtocol: func(msg string) error { return errors.New(msg) },
50
+ ErrChunk: func(msg string) error { return errors.New(msg) },
51
+ ErrUnknownMsgID: func(msg string) error { return errors.New(msg) },
52
+ ErrRecv: func(msg string) error { return errors.New(msg) },
53
+ }
54
+}
55
+
56
+func encodeReceiveTestPacket(declaredPayloadLen uint32, payload []byte) []byte {
57
+ hdr := protocol.Header{
58
+ Magic: protocol.MagicMsg,
59
+ Version: protocol.Version,
60
+ HeaderLen: protocol.HeaderLen,
61
+ Kind: protocol.KindRequest,
62
+ TransportStatus: protocol.StatusOK,
63
+ PayloadLen: declaredPayloadLen,
64
+ ItemCount: 1,
65
+ MessageID: 1,
66
+ }
67
+ packet := make([]byte, protocol.HeaderSize+len(payload))
68
+ if hdr.Encode(packet) != protocol.HeaderSize {
69
+ panic("header encode failed")
70
+ }
71
+ copy(packet[protocol.HeaderSize:], payload)
72
+ return packet
73
+}
src/go/pkg/netipc/transport/internal/framing/send.go
new
+177
@@ -0,0 +1,177 @@
1
+package framing
2
+
3
+import (
4
+ "fmt"
5
+
6
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
7
+)
8
+
9
+// HeaderPayloadLen validates header + payload before lengths are encoded into
10
+// the protocol's uint32 wire fields.
11
+func HeaderPayloadLen(payloadLen int) (int, uint32, bool) {
12
+ if payloadLen < 0 {
13
+ return 0, 0, false
14
+ }
15
+ totalMsg := uint64(protocol.HeaderSize) + uint64(payloadLen)
16
+ if totalMsg > uint64(^uint32(0)) || totalMsg > uint64(int(^uint(0)>>1)) {
17
+ return 0, 0, false
18
+ }
19
+ return int(totalMsg), uint32(payloadLen), true
20
+}
21
+
22
+// FillMessageHeader applies the common NetIPC message header fields.
23
+func FillMessageHeader(hdr *protocol.Header, payloadLen uint32) {
24
+ hdr.Magic = protocol.MagicMsg
25
+ hdr.Version = protocol.Version
26
+ hdr.HeaderLen = protocol.HeaderLen
27
+ hdr.PayloadLen = payloadLen
28
+}
29
+
30
+// Sender chunks one logical NetIPC message over a transport-specific packet
31
+// writer.
32
+type Sender struct {
33
+ PacketSize uint32
34
+
35
+ SendFirstPacket func(*protocol.Header, []byte, int) error
36
+ SendChunk func(protocol.ChunkHeader, []byte) error
37
+ ErrBadParam func(string) error
38
+}
39
+
40
+type SessionSendConfig struct {
41
+ RoleClient bool
42
+ PacketSize uint32
43
+
44
+ InflightIDs *map[uint64]struct{}
45
+ FailAllInflight func()
46
+ IsSendDisconnect func(error) bool
47
+
48
+ SendFirstPacket func(*protocol.Header, []byte, int) error
49
+ SendChunk func(protocol.ChunkHeader, []byte) error
50
+
51
+ ErrLimitExceeded func(string) error
52
+ ErrDuplicateMsgID func(string) error
53
+ ErrBadParam func(string) error
54
+}
55
+
56
+func SessionSend(config SessionSendConfig, hdr *protocol.Header, payload []byte) error {
57
+ totalMsg, payloadLen, ok := HeaderPayloadLen(len(payload))
58
+ if !ok {
59
+ return config.ErrLimitExceeded("total message length exceeds protocol limit")
60
+ }
61
+
62
+ tracked, err := trackOutboundRequest(config, hdr)
63
+ if err != nil {
64
+ return err
65
+ }
66
+
67
+ FillMessageHeader(hdr, payloadLen)
68
+ sendErr := Sender{
69
+ PacketSize: config.PacketSize,
70
+ SendFirstPacket: config.SendFirstPacket,
71
+ SendChunk: config.SendChunk,
72
+ ErrBadParam: config.ErrBadParam,
73
+ }.Send(hdr, payload, totalMsg)
74
+
75
+ if sendErr != nil && tracked {
76
+ if config.IsSendDisconnect(sendErr) {
77
+ config.FailAllInflight()
78
+ } else {
79
+ delete(*config.InflightIDs, hdr.MessageID)
80
+ }
81
+ }
82
+
83
+ return sendErr
84
+}
85
+
86
+func trackOutboundRequest(config SessionSendConfig, hdr *protocol.Header) (bool, error) {
87
+ if !config.RoleClient || hdr.Kind != protocol.KindRequest {
88
+ return false, nil
89
+ }
90
+
91
+ if *config.InflightIDs == nil {
92
+ *config.InflightIDs = make(map[uint64]struct{})
93
+ }
94
+ if _, exists := (*config.InflightIDs)[hdr.MessageID]; exists {
95
+ return false, config.ErrDuplicateMsgID(fmt.Sprintf("message_id %d", hdr.MessageID))
96
+ }
97
+ (*config.InflightIDs)[hdr.MessageID] = struct{}{}
98
+ return true, nil
99
+}
100
+
101
+// Send writes one complete logical message.
102
+func (s Sender) Send(hdr *protocol.Header, payload []byte, totalMsg int) error {
103
+ packetSize, err := s.packetSize()
104
+ if err != nil {
105
+ return err
106
+ }
107
+ if totalMsg <= packetSize {
108
+ return s.SendFirstPacket(hdr, payload, totalMsg)
109
+ }
110
+
111
+ chunkPayloadBudget := packetSize - protocol.HeaderSize
112
+ if chunkPayloadBudget <= 0 {
113
+ return s.ErrBadParam("packet_size too small")
114
+ }
115
+
116
+ firstChunkPayload := minInt(len(payload), chunkPayloadBudget)
117
+ remainingAfterFirst := len(payload) - firstChunkPayload
118
+ continuationChunks := uint32(0)
119
+ if remainingAfterFirst > 0 {
120
+ continuationChunks = uint32(1 + ((remainingAfterFirst - 1) / chunkPayloadBudget))
121
+ }
122
+ chunkCount := 1 + continuationChunks
123
+
124
+ if err := s.SendFirstPacket(hdr, payload[:firstChunkPayload],
125
+ protocol.HeaderSize+firstChunkPayload); err != nil {
126
+ return err
127
+ }
128
+
129
+ offset := firstChunkPayload
130
+ for ci := uint32(1); ci < chunkCount; ci++ {
131
+ remaining := len(payload) - offset
132
+ thisChunk := minInt(remaining, chunkPayloadBudget)
133
+ chunkLen, ok := checkedU32(thisChunk)
134
+ if !ok {
135
+ return s.ErrBadParam("chunk payload length exceeds protocol limit")
136
+ }
137
+
138
+ chk := protocol.ChunkHeader{
139
+ Magic: protocol.MagicChunk,
140
+ Version: protocol.Version,
141
+ Flags: 0,
142
+ MessageID: hdr.MessageID,
143
+ TotalMessageLen: uint32(totalMsg),
144
+ ChunkIndex: ci,
145
+ ChunkCount: chunkCount,
146
+ ChunkPayloadLen: chunkLen,
147
+ }
148
+ if err := s.SendChunk(chk, payload[offset:offset+thisChunk]); err != nil {
149
+ return err
150
+ }
151
+
152
+ offset += thisChunk
153
+ }
154
+
155
+ return nil
156
+}
157
+
158
+func (s Sender) packetSize() (int, error) {
159
+ if uint64(s.PacketSize) > uint64(int(^uint(0)>>1)) {
160
+ return 0, s.ErrBadParam("packet_size exceeds platform limit")
161
+ }
162
+ return int(s.PacketSize), nil
163
+}
164
+
165
+func minInt(a, b int) int {
166
+ if a < b {
167
+ return a
168
+ }
169
+ return b
170
+}
171
+
172
+func checkedU32(value int) (uint32, bool) {
173
+ if value < 0 || uint64(value) > uint64(^uint32(0)) {
174
+ return 0, false
175
+ }
176
+ return uint32(value), true
177
+}
src/go/pkg/netipc/transport/posix/shm_linux.go
+81
-62
@@ -156,15 +156,15 @@ func ShmServerCreate(runDir, serviceName string, sessionID uint64, reqCapacity,
156
regionSize := int(respOff + respCap)
157
158
// Try O_EXCL create first (fast path, no stale check needed).
159
- f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
159
+ f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) // #nosec G304 -- path is built from runDir, validated service name, and hex session ID.
160
if err != nil && os.IsExist(err) {
161
// File exists — do stale recovery and retry.
162
- stale := checkShmStale(path)
162
+ stale := checkShmStale(path, runDirAllowsStaleUnlink(runDir))
163
if stale == shmStaleLive {
164
return nil, fmt.Errorf("%w: live server owns SHM region", ErrShmOpen)
165
}
166
// Stale file was unlinked, retry create
167
- f, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
167
+ f, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) // #nosec G304 -- path is built from runDir, validated service name, and hex session ID.
168
}
169
if err != nil {
170
return nil, fmt.Errorf("%w: %v", ErrShmOpen, err)
@@ -172,16 +172,16 @@ func ShmServerCreate(runDir, serviceName string, sessionID uint64, reqCapacity,
172
fd := int(f.Fd())
173
174
if err := syscall.Ftruncate(fd, int64(regionSize)); err != nil {
175
- f.Close()
176
- os.Remove(path)
175
+ _ = f.Close()
176
+ _ = os.Remove(path)
177
return nil, fmt.Errorf("%w: %v", ErrShmTruncate, err)
178
}
179
180
data, err := syscall.Mmap(fd, 0, regionSize,
181
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
182
if err != nil {
183
- f.Close()
184
- os.Remove(path)
183
+ _ = f.Close()
184
+ _ = os.Remove(path)
185
return nil, fmt.Errorf("%w: %v", ErrShmMmap, err)
186
}
187
@@ -206,7 +206,7 @@ func ShmServerCreate(runDir, serviceName string, sessionID uint64, reqCapacity,
206
binary.NativeEndian.PutUint32(data[shmHeaderRespCapOff:shmHeaderRespCapOff+4], respCap)
207
208
// Release fence: ensure header writes are visible before clients
209
- atomic.StoreUint32((*uint32)(unsafe.Pointer(&data[shmHeaderReqSignalOff])), 0)
209
+ atomic.StoreUint32((*uint32)(unsafe.Pointer(&data[shmHeaderReqSignalOff])), 0) // #nosec G103 -- mmap header fields require raw aligned atomic access.
210
211
// Close the os.File but keep the fd open (Mmap holds a reference).
212
// Actually, we need to keep the fd ourselves for the context.
@@ -216,12 +216,12 @@ func ShmServerCreate(runDir, serviceName string, sessionID uint64, reqCapacity,
216
// The safe way: dup the fd, then close the file.
217
newFd, err := syscall.Dup(fd)
218
if err != nil {
219
- syscall.Munmap(data)
220
- f.Close()
221
- os.Remove(path)
219
+ _ = syscall.Munmap(data)
220
+ _ = f.Close()
221
+ _ = os.Remove(path)
222
return nil, fmt.Errorf("%w: dup: %v", ErrShmOpen, err)
223
}
224
- f.Close() // closes original fd
224
+ _ = f.Close() // closes original fd
225
226
return &ShmContext{
227
role: ShmRoleServer,
@@ -242,15 +242,15 @@ func ShmServerCreate(runDir, serviceName string, sessionID uint64, reqCapacity,
242
// ShmDestroy destroys a server SHM region (munmap, close, unlink).
243
func (c *ShmContext) ShmDestroy() {
244
if c.data != nil {
245
- syscall.Munmap(c.data)
245
+ _ = syscall.Munmap(c.data)
246
c.data = nil
247
}
248
if c.fd >= 0 {
249
- syscall.Close(c.fd)
249
+ _ = syscall.Close(c.fd)
250
c.fd = -1
251
}
252
if c.path != "" {
253
- os.Remove(c.path)
253
+ _ = os.Remove(c.path)
254
c.path = ""
255
}
256
}
@@ -266,7 +266,7 @@ func ShmClientAttach(runDir, serviceName string, sessionID uint64) (*ShmContext,
266
return nil, err
267
}
268
269
- f, err := os.OpenFile(path, os.O_RDWR, 0)
269
+ f, err := os.OpenFile(path, os.O_RDWR, 0) // #nosec G304 -- path is built from runDir, validated service name, and hex session ID.
270
if err != nil {
271
return nil, fmt.Errorf("%w: %v", ErrShmOpen, err)
272
}
@@ -274,45 +274,45 @@ func ShmClientAttach(runDir, serviceName string, sessionID uint64) (*ShmContext,
274
275
info, err := f.Stat()
276
if err != nil {
277
- f.Close()
277
+ _ = f.Close()
278
return nil, fmt.Errorf("%w: stat: %v", ErrShmOpen, err)
279
}
280
281
fileSize := int(info.Size())
282
if fileSize < int(shmHeaderLen) {
283
- f.Close()
283
+ _ = f.Close()
284
return nil, ErrShmNotReady
285
}
286
287
data, err := syscall.Mmap(fd, 0, fileSize,
288
syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
289
if err != nil {
290
- f.Close()
290
+ _ = f.Close()
291
return nil, fmt.Errorf("%w: %v", ErrShmMmap, err)
292
}
293
294
// Acquire fence
295
- atomic.LoadUint32((*uint32)(unsafe.Pointer(&data[shmHeaderReqSignalOff])))
295
+ atomic.LoadUint32((*uint32)(unsafe.Pointer(&data[shmHeaderReqSignalOff]))) // #nosec G103 -- mmap header fields require raw aligned atomic access.
296
297
// Validate header
298
magic := binary.NativeEndian.Uint32(data[shmHeaderMagicOff : shmHeaderMagicOff+4])
299
if magic != shmRegionMagic {
300
- syscall.Munmap(data)
301
- f.Close()
300
+ _ = syscall.Munmap(data)
301
+ _ = f.Close()
302
return nil, ErrShmBadMagic
303
}
304
305
version := binary.NativeEndian.Uint16(data[shmHeaderVersionOff : shmHeaderVersionOff+2])
306
if version != shmRegionVersion {
307
- syscall.Munmap(data)
308
- f.Close()
307
+ _ = syscall.Munmap(data)
308
+ _ = f.Close()
309
return nil, ErrShmBadVersion
310
}
311
312
hdrLen := binary.NativeEndian.Uint16(data[shmHeaderHeaderLenOff : shmHeaderHeaderLenOff+2])
313
if hdrLen != uint16(shmHeaderLen) {
314
- syscall.Munmap(data)
315
- f.Close()
314
+ _ = syscall.Munmap(data)
315
+ _ = f.Close()
316
return nil, ErrShmBadHeader
317
}
318
@@ -323,8 +323,8 @@ func ShmClientAttach(runDir, serviceName string, sessionID uint64) (*ShmContext,
323
324
headerEnd := shmAlign64(uint32(shmHeaderLen))
325
if reqOff < headerEnd || reqCap == 0 || respOff < headerEnd || respCap == 0 {
326
- syscall.Munmap(data)
327
- f.Close()
326
+ _ = syscall.Munmap(data)
327
+ _ = f.Close()
328
return nil, ErrShmNotReady
329
}
330
@@ -333,8 +333,8 @@ func ShmClientAttach(runDir, serviceName string, sessionID uint64) (*ShmContext,
333
respOff%shmRegionAlignment != 0 ||
334
respCap%shmRegionAlignment != 0 ||
335
respOff < shmAlign64(reqOff+reqCap) {
336
- syscall.Munmap(data)
337
- f.Close()
336
+ _ = syscall.Munmap(data)
337
+ _ = f.Close()
338
return nil, ErrShmBadSize
339
}
340
@@ -343,22 +343,22 @@ func ShmClientAttach(runDir, serviceName string, sessionID uint64) (*ShmContext,
343
respEnd := int(respOff) + int(respCap)
344
needed := max(respEnd, reqEnd)
345
if fileSize < needed {
346
- syscall.Munmap(data)
347
- f.Close()
346
+ _ = syscall.Munmap(data)
347
+ _ = f.Close()
348
return nil, ErrShmBadSize
349
}
350
351
// Read current sequence numbers
352
curReqSeq, err := atomicLoadU64(data, shmHeaderReqSeqOff)
353
if err != nil {
354
- syscall.Munmap(data)
355
- f.Close()
354
+ _ = syscall.Munmap(data)
355
+ _ = f.Close()
356
return nil, fmt.Errorf("%w: load req_seq: %v", ErrShmBadParam, err)
357
}
358
curRespSeq, err := atomicLoadU64(data, shmHeaderRespSeqOff)
359
if err != nil {
360
- syscall.Munmap(data)
361
- f.Close()
360
+ _ = syscall.Munmap(data)
361
+ _ = f.Close()
362
return nil, fmt.Errorf("%w: load resp_seq: %v", ErrShmBadParam, err)
363
}
364
ownerGen := binary.NativeEndian.Uint32(data[shmHeaderOwnerGenOff : shmHeaderOwnerGenOff+4])
@@ -366,11 +366,11 @@ func ShmClientAttach(runDir, serviceName string, sessionID uint64) (*ShmContext,
366
// Dup fd and close file
367
newFd, err := syscall.Dup(fd)
368
if err != nil {
369
- syscall.Munmap(data)
370
- f.Close()
369
+ _ = syscall.Munmap(data)
370
+ _ = f.Close()
371
return nil, fmt.Errorf("%w: dup: %v", ErrShmOpen, err)
372
}
373
- f.Close()
373
+ _ = f.Close()
374
375
return &ShmContext{
376
role: ShmRoleClient,
@@ -391,11 +391,11 @@ func ShmClientAttach(runDir, serviceName string, sessionID uint64) (*ShmContext,
391
// ShmClose closes a client SHM context (no unlink).
392
func (c *ShmContext) ShmClose() {
393
if c.data != nil {
394
- syscall.Munmap(c.data)
394
+ _ = syscall.Munmap(c.data)
395
c.data = nil
396
}
397
if c.fd >= 0 {
398
- syscall.Close(c.fd)
398
+ _ = syscall.Close(c.fd)
399
c.fd = -1
400
}
401
}
@@ -527,7 +527,7 @@ func (c *ShmContext) ShmReceive(buf []byte, timeoutMs uint32) (int, error) {
527
var deadlineNs uint64
528
if timeoutMs > 0 {
529
var nowTs syscall.Timespec
530
- syscall.Syscall(syscall.SYS_CLOCK_GETTIME, 1 /* CLOCK_MONOTONIC */, uintptr(unsafe.Pointer(&nowTs)), 0)
530
+ syscall.Syscall(syscall.SYS_CLOCK_GETTIME, 1 /* CLOCK_MONOTONIC */, uintptr(unsafe.Pointer(&nowTs)), 0) // #nosec G103 -- syscall requires passing a Timespec pointer.
531
deadlineNs = uint64(nowTs.Sec)*1_000_000_000 + uint64(nowTs.Nsec) +
532
uint64(timeoutMs)*1_000_000
533
}
@@ -550,7 +550,7 @@ func (c *ShmContext) ShmReceive(buf []byte, timeoutMs uint32) (int, error) {
550
var ts *syscall.Timespec
551
if deadlineNs > 0 {
552
var nowTs syscall.Timespec
553
- syscall.Syscall(syscall.SYS_CLOCK_GETTIME, 1 /* CLOCK_MONOTONIC */, uintptr(unsafe.Pointer(&nowTs)), 0)
553
+ syscall.Syscall(syscall.SYS_CLOCK_GETTIME, 1 /* CLOCK_MONOTONIC */, uintptr(unsafe.Pointer(&nowTs)), 0) // #nosec G103 -- syscall requires passing a Timespec pointer.
554
nowVal := uint64(nowTs.Sec)*1_000_000_000 + uint64(nowTs.Nsec)
555
if nowVal >= deadlineNs {
556
return 0, ErrShmTimeout
@@ -654,7 +654,7 @@ func atomicLoadU64(data []byte, off int) (uint64, error) {
654
if off < 0 || off+8 > len(data) {
655
return 0, errShmOutOfBounds
656
}
657
- ptr := (*uint64)(unsafe.Pointer(&data[off]))
657
+ ptr := (*uint64)(unsafe.Pointer(&data[off])) // #nosec G103 -- mmap header fields require raw aligned atomic access.
658
return atomic.LoadUint64(ptr), nil
659
}
660
@@ -662,7 +662,7 @@ func atomicLoadU32(data []byte, off int) (uint32, error) {
662
if off < 0 || off+4 > len(data) {
663
return 0, errShmOutOfBounds
664
}
665
- ptr := (*uint32)(unsafe.Pointer(&data[off]))
665
+ ptr := (*uint32)(unsafe.Pointer(&data[off])) // #nosec G103 -- mmap header fields require raw aligned atomic access.
666
return atomic.LoadUint32(ptr), nil
667
}
668
@@ -670,7 +670,7 @@ func atomicStoreU32(data []byte, off int, val uint32) error {
670
if off < 0 || off+4 > len(data) {
671
return errShmOutOfBounds
672
}
673
- ptr := (*uint32)(unsafe.Pointer(&data[off]))
673
+ ptr := (*uint32)(unsafe.Pointer(&data[off])) // #nosec G103 -- mmap header fields require raw aligned atomic access.
674
atomic.StoreUint32(ptr, val)
675
return nil
676
}
@@ -679,7 +679,7 @@ func atomicAddU64(data []byte, off int, val uint64) error {
679
if off < 0 || off+8 > len(data) {
680
return errShmOutOfBounds
681
}
682
- ptr := (*uint64)(unsafe.Pointer(&data[off]))
682
+ ptr := (*uint64)(unsafe.Pointer(&data[off])) // #nosec G103 -- mmap header fields require raw aligned atomic access.
683
atomic.AddUint64(ptr, val)
684
return nil
685
}
@@ -688,7 +688,7 @@ func atomicAddU32(data []byte, off int, val uint32) error {
688
if off < 0 || off+4 > len(data) {
689
return errShmOutOfBounds
690
}
691
- ptr := (*uint32)(unsafe.Pointer(&data[off]))
691
+ ptr := (*uint32)(unsafe.Pointer(&data[off])) // #nosec G103 -- mmap header fields require raw aligned atomic access.
692
atomic.AddUint32(ptr, val)
693
return nil
694
}
@@ -697,7 +697,7 @@ func futexWakeCall(data []byte, off int, count int) int {
697
if off < 0 || off+4 > len(data) {
698
return -1
699
}
700
- addr := unsafe.Pointer(&data[off])
700
+ addr := unsafe.Pointer(&data[off]) // #nosec G103 -- futex needs the address of the mmap signal word.
701
r1, _, _ := syscall.Syscall6(
702
syscall.SYS_FUTEX,
703
uintptr(addr),
@@ -712,10 +712,10 @@ func futexWaitCall(data []byte, off int, expected uint32, ts *syscall.Timespec)
712
if off < 0 || off+4 > len(data) {
713
return -1
714
}
715
- addr := unsafe.Pointer(&data[off])
715
+ addr := unsafe.Pointer(&data[off]) // #nosec G103 -- futex needs the address of the mmap signal word.
716
var tsPtr uintptr
717
if ts != nil {
718
- tsPtr = uintptr(unsafe.Pointer(ts))
718
+ tsPtr = uintptr(unsafe.Pointer(ts)) // #nosec G103 -- futex syscall requires an optional Timespec pointer.
719
}
720
r1, _, errno := syscall.Syscall6(
721
syscall.SYS_FUTEX,
@@ -742,6 +742,7 @@ func ShmCleanupStale(runDir, serviceName string) {
742
if err != nil {
743
return
744
}
745
+ allowStaleUnlink := runDirAllowsStaleUnlink(runDir)
746
prefix := serviceName + "-"
747
suffix := ".ipcshm"
748
for _, e := range entries {
@@ -756,7 +757,7 @@ func ShmCleanupStale(runDir, serviceName string) {
757
continue
758
}
759
path := filepath.Join(runDir, name)
759
- result := checkShmStale(path)
760
+ result := checkShmStale(path, allowStaleUnlink)
761
_ = result // checkShmStale already unlinks stale/invalid files
762
}
763
}
@@ -774,47 +775,65 @@ const (
775
shmStaleInvalid
776
)
777
777
-func checkShmStale(path string) shmStaleResult {
778
+func removeStalePath(path string, allowStaleUnlink bool) bool {
779
+ if !allowStaleUnlink {
780
+ return false
781
+ }
782
+ err := os.Remove(path)
783
+ return err == nil || os.IsNotExist(err)
784
+}
785
+
786
+func checkShmStale(path string, allowStaleUnlink bool) shmStaleResult {
787
info, err := os.Stat(path)
788
if err != nil {
789
return shmStaleNotExist
790
}
791
792
if info.Size() < int64(shmHeaderLen) {
784
- os.Remove(path)
793
+ if !removeStalePath(path, allowStaleUnlink) {
794
+ return shmStaleLive
795
+ }
796
return shmStaleInvalid
797
}
798
788
- f, err := os.Open(path)
799
+ f, err := os.Open(path) // #nosec G304 -- path comes from the validated SHM cleanup scan or buildShmPath.
800
if err != nil {
790
- os.Remove(path)
801
+ if !removeStalePath(path, allowStaleUnlink) {
802
+ return shmStaleLive
803
+ }
804
return shmStaleInvalid
805
}
806
807
data, err := syscall.Mmap(int(f.Fd()), 0, int(shmHeaderLen),
808
syscall.PROT_READ, syscall.MAP_SHARED)
796
- f.Close()
809
+ _ = f.Close()
810
if err != nil {
798
- os.Remove(path)
811
+ if !removeStalePath(path, allowStaleUnlink) {
812
+ return shmStaleLive
813
+ }
814
return shmStaleInvalid
815
}
816
817
magic := binary.NativeEndian.Uint32(data[shmHeaderMagicOff : shmHeaderMagicOff+4])
818
if magic != shmRegionMagic {
804
- syscall.Munmap(data)
805
- os.Remove(path)
819
+ _ = syscall.Munmap(data)
820
+ if !removeStalePath(path, allowStaleUnlink) {
821
+ return shmStaleLive
822
+ }
823
return shmStaleInvalid
824
}
825
826
ownerPid := int(int32(binary.NativeEndian.Uint32(data[shmHeaderOwnerPidOff : shmHeaderOwnerPidOff+4])))
827
ownerGen := binary.NativeEndian.Uint32(data[shmHeaderOwnerGenOff : shmHeaderOwnerGenOff+4])
811
- syscall.Munmap(data)
828
+ _ = syscall.Munmap(data)
829
830
if pidAlive(ownerPid) && ownerGen != 0 {
831
return shmStaleLive
832
}
833
834
// Dead owner or zero generation (PID reuse / legacy) — stale
818
- os.Remove(path)
835
+ if !removeStalePath(path, allowStaleUnlink) {
836
+ return shmStaleLive
837
+ }
838
return shmStaleRecovered
839
}
src/go/pkg/netipc/transport/posix/shm_more_edge_test.go
+13
-9
@@ -48,7 +48,11 @@ func writeRawShmRegionFile(t *testing.T, runDir, service string, sessionID uint6
48
if err != nil {
49
t.Fatalf("open %s: %v", path, err)
50
}
51
- defer f.Close()
51
+ defer func() {
52
+ if err := f.Close(); err != nil {
53
+ t.Fatalf("close %s: %v", path, err)
54
+ }
55
+ }()
56
57
if err := f.Truncate(int64(size)); err != nil {
58
t.Fatalf("truncate %s: %v", path, err)
@@ -343,12 +347,12 @@ func TestCheckShmStaleVariants(t *testing.T) {
347
validSize := int(respOff + respCap)
348
349
missingPath := filepath.Join(runDir, "missing.ipcshm")
346
- if got := checkShmStale(missingPath); got != shmStaleNotExist {
350
+ if got := checkShmStale(missingPath, true); got != shmStaleNotExist {
351
t.Fatalf("missing path result = %v, want %v", got, shmStaleNotExist)
352
}
353
354
tinyPath := writeRawShmRegionFile(t, runDir, "go_shm_check_tiny", 1, 8, nil)
351
- if got := checkShmStale(tinyPath); got != shmStaleInvalid {
355
+ if got := checkShmStale(tinyPath, true); got != shmStaleInvalid {
356
t.Fatalf("tiny file result = %v, want %v", got, shmStaleInvalid)
357
}
358
if _, err := os.Stat(tinyPath); !errors.Is(err, os.ErrNotExist) {
@@ -359,14 +363,14 @@ func TestCheckShmStaleVariants(t *testing.T) {
363
fillShmHeader(data, int32(os.Getpid()), 1, reqOff, reqCap, respOff, respCap)
364
binary.NativeEndian.PutUint32(data[shmHeaderMagicOff:shmHeaderMagicOff+4], 0)
365
})
362
- if got := checkShmStale(badMagicPath); got != shmStaleInvalid {
366
+ if got := checkShmStale(badMagicPath, true); got != shmStaleInvalid {
367
t.Fatalf("bad magic result = %v, want %v", got, shmStaleInvalid)
368
}
369
370
livePath := writeRawShmRegionFile(t, runDir, "go_shm_check_live", 3, validSize, func(data []byte) {
371
fillShmHeader(data, int32(os.Getpid()), 7, reqOff, reqCap, respOff, respCap)
372
})
369
- if got := checkShmStale(livePath); got != shmStaleLive {
373
+ if got := checkShmStale(livePath, true); got != shmStaleLive {
374
t.Fatalf("live path result = %v, want %v", got, shmStaleLive)
375
}
376
if _, err := os.Stat(livePath); err != nil {
@@ -376,7 +380,7 @@ func TestCheckShmStaleVariants(t *testing.T) {
380
legacyPath := writeRawShmRegionFile(t, runDir, "go_shm_check_legacy", 4, validSize, func(data []byte) {
381
fillShmHeader(data, int32(os.Getpid()), 0, reqOff, reqCap, respOff, respCap)
382
})
379
- if got := checkShmStale(legacyPath); got != shmStaleRecovered {
383
+ if got := checkShmStale(legacyPath, true); got != shmStaleRecovered {
384
t.Fatalf("legacy path result = %v, want %v", got, shmStaleRecovered)
385
}
386
if _, err := os.Stat(legacyPath); !errors.Is(err, os.ErrNotExist) {
@@ -389,7 +393,7 @@ func TestCheckShmStaleVariants(t *testing.T) {
393
if err := os.Chmod(unreadablePath, 0); err != nil {
394
t.Fatalf("chmod unreadable stale file: %v", err)
395
}
392
- if got := checkShmStale(unreadablePath); got != shmStaleInvalid {
396
+ if got := checkShmStale(unreadablePath, true); got != shmStaleInvalid {
397
t.Fatalf("unreadable path result = %v, want %v", got, shmStaleInvalid)
398
}
399
if _, err := os.Stat(unreadablePath); !errors.Is(err, os.ErrNotExist) {
@@ -403,8 +407,8 @@ func TestCheckShmStaleVariants(t *testing.T) {
407
if err := os.MkdirAll(filepath.Join(dirPath, "keep"), 0700); err != nil {
408
t.Fatalf("mkdir stale dir: %v", err)
409
}
406
- if got := checkShmStale(dirPath); got != shmStaleInvalid {
407
- t.Fatalf("directory path result = %v, want %v", got, shmStaleInvalid)
410
+ if got := checkShmStale(dirPath, true); got != shmStaleLive {
411
+ t.Fatalf("directory path result = %v, want %v", got, shmStaleLive)
412
}
413
if info, err := os.Stat(dirPath); err != nil || !info.IsDir() {
414
t.Fatalf("non-empty stale directory should remain, stat err = %v", err)
src/go/pkg/netipc/transport/posix/uds.go
+3
-882
@@ -10,17 +10,11 @@
10
package posix
11
12
import (
13
- "encoding/binary"
13
"errors"
14
"fmt"
16
- "net"
17
- "os"
15
"path/filepath"
19
- "sync/atomic"
16
"syscall"
17
"unsafe"
22
-
23
- "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
18
)
19
20
// ---------------------------------------------------------------------------
@@ -68,529 +62,6 @@ func wrapErr(sentinel error, detail string) error {
62
return fmt.Errorf("%w: %s", sentinel, detail)
63
}
64
71
-func headerVersionIncompatible(buf []byte, expectedCode uint16) bool {
72
- if len(buf) < protocol.HeaderSize {
73
- return false
74
- }
75
-
76
- return binary.NativeEndian.Uint32(buf[0:4]) == protocol.MagicMsg &&
77
- binary.NativeEndian.Uint16(buf[4:6]) != protocol.Version &&
78
- binary.NativeEndian.Uint16(buf[6:8]) == protocol.HeaderLen &&
79
- binary.NativeEndian.Uint16(buf[8:10]) == protocol.KindControl &&
80
- binary.NativeEndian.Uint16(buf[12:14]) == expectedCode
81
-}
82
-
83
-func helloLayoutIncompatible(buf []byte) bool {
84
- return len(buf) >= 2 && binary.NativeEndian.Uint16(buf[0:2]) != 1
85
-}
86
-
87
-func helloAckLayoutIncompatible(buf []byte) bool {
88
- return len(buf) >= 2 && binary.NativeEndian.Uint16(buf[0:2]) != 1
89
-}
90
-
91
-// ---------------------------------------------------------------------------
92
-// Role
93
-// ---------------------------------------------------------------------------
94
-
95
-// Role distinguishes client vs server sessions.
96
-type Role int
97
-
98
-const (
99
- RoleClient Role = 1
100
- RoleServer Role = 2
101
-)
102
-
103
-// ---------------------------------------------------------------------------
104
-// Configuration
105
-// ---------------------------------------------------------------------------
106
-
107
-// ClientConfig configures a client connection.
108
-type ClientConfig struct {
109
- SupportedProfiles uint32
110
- PreferredProfiles uint32
111
- MaxRequestPayloadBytes uint32 // 0 = use default (1024)
112
- MaxRequestBatchItems uint32 // 0 = use default (1)
113
- MaxResponsePayloadBytes uint32
114
- MaxResponseBatchItems uint32
115
- AuthToken uint64
116
- PacketSize uint32 // 0 = auto-detect from SO_SNDBUF
117
-}
118
-
119
-// ServerConfig configures a listener and its accepted sessions.
120
-type ServerConfig struct {
121
- SupportedProfiles uint32
122
- PreferredProfiles uint32
123
- MaxRequestPayloadBytes uint32
124
- MaxRequestBatchItems uint32
125
- MaxResponsePayloadBytes uint32
126
- MaxResponseBatchItems uint32
127
- AuthToken uint64
128
- PacketSize uint32 // 0 = auto-detect from SO_SNDBUF
129
- Backlog int // 0 = default (16)
130
-}
131
-
132
-// ---------------------------------------------------------------------------
133
-// Session
134
-// ---------------------------------------------------------------------------
135
-
136
-// Session is a connected UDS SEQPACKET session (client or server side).
137
-type Session struct {
138
- fd int
139
- role Role
140
-
141
- // Negotiated limits
142
- MaxRequestPayloadBytes uint32
143
- MaxRequestBatchItems uint32
144
- MaxResponsePayloadBytes uint32
145
- MaxResponseBatchItems uint32
146
- PacketSize uint32
147
- SelectedProfile uint32
148
- SessionID uint64
149
-
150
- // Internal receive buffer for chunked reassembly
151
- recvBuf []byte
152
-
153
- // Reusable packet scratch buffer for receive chunk assembly.
154
- pktBuf []byte
155
-
156
- // In-flight message_id set (client-side only)
157
- inflightIDs map[uint64]struct{}
158
-}
159
-
160
-func (s *Session) failAllInflight() {
161
- if s.role != RoleClient || len(s.inflightIDs) == 0 {
162
- return
163
- }
164
- clear(s.inflightIDs)
165
-}
166
-
167
-// Fd returns the raw file descriptor for poll/epoll integration.
168
-func (s *Session) Fd() int {
169
- return s.fd
170
-}
171
-
172
-// Role returns the session role.
173
-func (s *Session) Role() Role {
174
- return s.role
175
-}
176
-
177
-// Close closes the session and releases resources.
178
-func (s *Session) Close() {
179
- if s.fd >= 0 {
180
- syscall.Close(s.fd)
181
- s.fd = -1
182
- }
183
- s.recvBuf = nil
184
- s.pktBuf = nil
185
- s.failAllInflight()
186
-}
187
-
188
-// Connect establishes a session to a server at {runDir}/{serviceName}.sock.
189
-// Performs the full handshake. Blocks until connected + handshake done.
190
-func Connect(runDir, serviceName string, config *ClientConfig) (*Session, error) {
191
- path, err := buildSocketPath(runDir, serviceName)
192
- if err != nil {
193
- return nil, err
194
- }
195
-
196
- fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
197
- if err != nil {
198
- return nil, wrapErr(ErrSocket, err.Error())
199
- }
200
-
201
- session, herr := connectAndHandshake(fd, path, config)
202
- if herr != nil {
203
- syscall.Close(fd)
204
- return nil, herr
205
- }
206
- return session, nil
207
-}
208
-
209
-// Send sends one logical message. The caller fills Kind, Code, Flags,
210
-// ItemCount, MessageID in hdr; this function sets Magic/Version/
211
-// HeaderLen/PayloadLen. If the total message exceeds PacketSize,
212
-// it is chunked transparently.
213
-func (s *Session) Send(hdr *protocol.Header, payload []byte) error {
214
- if s.fd < 0 {
215
- return wrapErr(ErrBadParam, "session closed")
216
- }
217
-
218
- // Client-side: track in-flight message_ids for requests
219
- if s.role == RoleClient && hdr.Kind == protocol.KindRequest {
220
- if s.inflightIDs == nil {
221
- s.inflightIDs = make(map[uint64]struct{})
222
- }
223
- if _, exists := s.inflightIDs[hdr.MessageID]; exists {
224
- return wrapErr(ErrDuplicateMsgID, fmt.Sprintf("message_id %d", hdr.MessageID))
225
- }
226
- s.inflightIDs[hdr.MessageID] = struct{}{}
227
- }
228
-
229
- // Fill envelope fields
230
- hdr.Magic = protocol.MagicMsg
231
- hdr.Version = protocol.Version
232
- hdr.HeaderLen = protocol.HeaderLen
233
- hdr.PayloadLen = uint32(len(payload))
234
-
235
- tracked := s.role == RoleClient && hdr.Kind == protocol.KindRequest
236
-
237
- sendErr := s.sendInner(hdr, payload)
238
-
239
- // Rollback: remove message_id from in-flight set on send failure
240
- if sendErr != nil && tracked {
241
- if errors.Is(sendErr, ErrSend) {
242
- s.failAllInflight()
243
- } else {
244
- delete(s.inflightIDs, hdr.MessageID)
245
- }
246
- }
247
-
248
- return sendErr
249
-}
250
-
251
-// sendInner performs the actual send logic, separated so the caller can
252
-// rollback the in-flight set on failure.
253
-func (s *Session) sendInner(hdr *protocol.Header, payload []byte) error {
254
- totalMsg := protocol.HeaderSize + len(payload)
255
-
256
- // Single packet?
257
- if totalMsg <= int(s.PacketSize) {
258
- var hdrBuf [protocol.HeaderSize]byte
259
- hdr.Encode(hdrBuf[:])
260
- return rawSendIov(s.fd, hdrBuf[:], payload)
261
- }
262
-
263
- // Chunked send
264
- chunkPayloadBudget := int(s.PacketSize) - protocol.HeaderSize
265
- if chunkPayloadBudget <= 0 {
266
- return wrapErr(ErrBadParam, "packet_size too small")
267
- }
268
-
269
- firstChunkPayload := min(len(payload), chunkPayloadBudget)
270
-
271
- remainingAfterFirst := len(payload) - firstChunkPayload
272
- continuationChunks := uint32(0)
273
- if remainingAfterFirst > 0 {
274
- continuationChunks = uint32((remainingAfterFirst + chunkPayloadBudget - 1) / chunkPayloadBudget)
275
- }
276
- chunkCount := 1 + continuationChunks
277
-
278
- // Send first chunk: outer header + first part of payload
279
- var hdrBuf [protocol.HeaderSize]byte
280
- hdr.Encode(hdrBuf[:])
281
- if err := rawSendIov(s.fd, hdrBuf[:], payload[:firstChunkPayload]); err != nil {
282
- return err
283
- }
284
-
285
- // Send continuation chunks
286
- offset := firstChunkPayload
287
- for ci := uint32(1); ci < chunkCount; ci++ {
288
- remaining := len(payload) - offset
289
- thisChunk := min(remaining, chunkPayloadBudget)
290
-
291
- chk := protocol.ChunkHeader{
292
- Magic: protocol.MagicChunk,
293
- Version: protocol.Version,
294
- Flags: 0,
295
- MessageID: hdr.MessageID,
296
- TotalMessageLen: uint32(totalMsg),
297
- ChunkIndex: ci,
298
- ChunkCount: chunkCount,
299
- ChunkPayloadLen: uint32(thisChunk),
300
- }
301
-
302
- var chkBuf [protocol.HeaderSize]byte
303
- chk.Encode(chkBuf[:])
304
- if err := rawSendIov(s.fd, chkBuf[:], payload[offset:offset+thisChunk]); err != nil {
305
- return err
306
- }
307
-
308
- offset += thisChunk
309
- }
310
-
311
- return nil
312
-}
313
-
314
-// Receive reads one logical message. Blocks until a complete message
315
-// arrives. buf is a caller-provided scratch buffer for the first packet.
316
-// On success, returns the header and a payload view valid until the next
317
-// Receive call on this session.
318
-func (s *Session) Receive(buf []byte) (protocol.Header, []byte, error) {
319
- if s.fd < 0 {
320
- return protocol.Header{}, nil, wrapErr(ErrBadParam, "session closed")
321
- }
322
-
323
- // Read first packet
324
- n, err := rawRecv(s.fd, buf)
325
- if err != nil {
326
- if errors.Is(err, ErrRecv) {
327
- s.failAllInflight()
328
- }
329
- return protocol.Header{}, nil, err
330
- }
331
-
332
- if n < protocol.HeaderSize {
333
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "packet too short for header")
334
- }
335
-
336
- hdr, err := protocol.DecodeHeader(buf[:n])
337
- if err != nil {
338
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "header decode: "+err.Error())
339
- }
340
-
341
- // Validate payload_len against negotiated directional limit.
342
- // Server receives requests; client receives responses.
343
- var maxPayload uint32
344
- if s.role == RoleServer {
345
- maxPayload = s.MaxRequestPayloadBytes
346
- } else {
347
- maxPayload = s.MaxResponsePayloadBytes
348
- }
349
- if hdr.PayloadLen > maxPayload {
350
- return protocol.Header{}, nil, wrapErr(ErrLimitExceeded,
351
- fmt.Sprintf("payload_len %d exceeds negotiated max %d", hdr.PayloadLen, maxPayload))
352
- }
353
-
354
- // Validate item_count against negotiated directional batch limit.
355
- var maxBatch uint32
356
- if s.role == RoleServer {
357
- maxBatch = s.MaxRequestBatchItems
358
- } else {
359
- maxBatch = s.MaxResponseBatchItems
360
- }
361
- if hdr.ItemCount > maxBatch {
362
- return protocol.Header{}, nil, wrapErr(ErrLimitExceeded,
363
- fmt.Sprintf("item_count %d exceeds negotiated max %d", hdr.ItemCount, maxBatch))
364
- }
365
-
366
- // Client-side: validate response message_id is in-flight
367
- if s.role == RoleClient && hdr.Kind == protocol.KindResponse {
368
- if s.inflightIDs == nil {
369
- return protocol.Header{}, nil, wrapErr(ErrUnknownMsgID,
370
- fmt.Sprintf("message_id %d", hdr.MessageID))
371
- }
372
- if _, exists := s.inflightIDs[hdr.MessageID]; !exists {
373
- return protocol.Header{}, nil, wrapErr(ErrUnknownMsgID,
374
- fmt.Sprintf("message_id %d", hdr.MessageID))
375
- }
376
- delete(s.inflightIDs, hdr.MessageID)
377
- }
378
-
379
- totalMsg := protocol.HeaderSize + int(hdr.PayloadLen)
380
-
381
- // Non-chunked: entire message in one packet
382
- if n >= totalMsg {
383
- payload := buf[protocol.HeaderSize : protocol.HeaderSize+int(hdr.PayloadLen)]
384
-
385
- // Validate batch directory
386
- if hdr.Flags&protocol.FlagBatch != 0 && hdr.ItemCount > 1 {
387
- dirBytes := int(hdr.ItemCount) * 8
388
- dirAligned := protocol.Align8(dirBytes)
389
- if len(payload) < dirAligned {
390
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "batch dir exceeds payload")
391
- }
392
- packedAreaLen := uint32(len(payload) - dirAligned)
393
- if err := protocol.BatchDirValidate(payload[:dirBytes], hdr.ItemCount, packedAreaLen); err != nil {
394
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "batch dir: "+err.Error())
395
- }
396
- }
397
-
398
- return hdr, payload, nil
399
- }
400
-
401
- // Chunked: first packet has partial payload
402
- firstPayloadBytes := n - protocol.HeaderSize
403
-
404
- // Ensure recv buffer is large enough
405
- needed := int(hdr.PayloadLen)
406
- if len(s.recvBuf) < needed {
407
- s.recvBuf = make([]byte, needed)
408
- }
409
-
410
- // Copy first chunk's payload
411
- copy(s.recvBuf[:firstPayloadBytes], buf[protocol.HeaderSize:protocol.HeaderSize+firstPayloadBytes])
412
-
413
- assembled := firstPayloadBytes
414
- chunkPayloadBudget := int(s.PacketSize) - protocol.HeaderSize
415
-
416
- // Expected chunk count
417
- remainingAfterFirst := int(hdr.PayloadLen) - firstPayloadBytes
418
- expectedContinuations := uint32(0)
419
- if remainingAfterFirst > 0 && chunkPayloadBudget > 0 {
420
- expectedContinuations = uint32((remainingAfterFirst + chunkPayloadBudget - 1) / chunkPayloadBudget)
421
- }
422
- expectedChunkCount := 1 + expectedContinuations
423
-
424
- // Temporary buffer for continuation packets
425
- pktBuf := ensureScratchBuf(&s.pktBuf, int(s.PacketSize))
426
-
427
- ci := uint32(1)
428
- for assembled < int(hdr.PayloadLen) {
429
- cn, err := rawRecv(s.fd, pktBuf)
430
- if err != nil {
431
- if errors.Is(err, ErrRecv) {
432
- s.failAllInflight()
433
- }
434
- return protocol.Header{}, nil, wrapErr(ErrRecv, "continuation recv: "+err.Error())
435
- }
436
-
437
- if cn < protocol.HeaderSize {
438
- return protocol.Header{}, nil, wrapErr(ErrChunk, "continuation too short")
439
- }
440
-
441
- chk, err := protocol.DecodeChunkHeader(pktBuf[:cn])
442
- if err != nil {
443
- return protocol.Header{}, nil, wrapErr(ErrChunk, "chunk header: "+err.Error())
444
- }
445
-
446
- // Validate chunk header
447
- if chk.MessageID != hdr.MessageID {
448
- return protocol.Header{}, nil, wrapErr(ErrChunk, "message_id mismatch")
449
- }
450
- if chk.ChunkIndex != ci {
451
- return protocol.Header{}, nil, wrapErr(ErrChunk, fmt.Sprintf(
452
- "chunk_index mismatch: expected %d, got %d", ci, chk.ChunkIndex))
453
- }
454
- if chk.ChunkCount != expectedChunkCount {
455
- return protocol.Header{}, nil, wrapErr(ErrChunk, "chunk_count mismatch")
456
- }
457
- if chk.TotalMessageLen != uint32(totalMsg) {
458
- return protocol.Header{}, nil, wrapErr(ErrChunk, "total_message_len mismatch")
459
- }
460
-
461
- chunkData := cn - protocol.HeaderSize
462
- if chunkData != int(chk.ChunkPayloadLen) {
463
- return protocol.Header{}, nil, wrapErr(ErrChunk, "chunk_payload_len mismatch")
464
- }
465
- if assembled+chunkData > int(hdr.PayloadLen) {
466
- return protocol.Header{}, nil, wrapErr(ErrChunk, "chunk exceeds payload_len")
467
- }
468
-
469
- copy(s.recvBuf[assembled:assembled+chunkData], pktBuf[protocol.HeaderSize:protocol.HeaderSize+chunkData])
470
- assembled += chunkData
471
- ci++
472
- }
473
-
474
- payload := s.recvBuf[:hdr.PayloadLen]
475
-
476
- // Validate batch directory
477
- if hdr.Flags&protocol.FlagBatch != 0 && hdr.ItemCount > 1 {
478
- dirBytes := int(hdr.ItemCount) * 8
479
- dirAligned := protocol.Align8(dirBytes)
480
- if len(payload) < dirAligned {
481
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "batch dir exceeds payload")
482
- }
483
- packedAreaLen := uint32(len(payload) - dirAligned)
484
- if err := protocol.BatchDirValidate(payload[:dirBytes], hdr.ItemCount, packedAreaLen); err != nil {
485
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "batch dir: "+err.Error())
486
- }
487
- }
488
-
489
- return hdr, payload, nil
490
-}
491
-
492
-// ---------------------------------------------------------------------------
493
-// Listener
494
-// ---------------------------------------------------------------------------
495
-
496
-// Listener is a listening UDS SEQPACKET endpoint.
497
-type Listener struct {
498
- fd int
499
- config ServerConfig
500
- path string
501
- nextSessionID atomic.Uint64
502
-}
503
-
504
-// Listen creates a listener on {runDir}/{serviceName}.sock.
505
-// Performs stale endpoint recovery.
506
-func Listen(runDir, serviceName string, config ServerConfig) (*Listener, error) {
507
- path, err := buildSocketPath(runDir, serviceName)
508
- if err != nil {
509
- return nil, err
510
- }
511
-
512
- // Stale recovery
513
- stale := checkAndRecoverStale(path)
514
- if stale == staleLiveServer {
515
- return nil, ErrAddrInUse
516
- }
517
-
518
- fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
519
- if err != nil {
520
- return nil, wrapErr(ErrSocket, err.Error())
521
- }
522
-
523
- // Bind
524
- sa := &syscall.SockaddrUnix{Name: path}
525
- if err := syscall.Bind(fd, sa); err != nil {
526
- syscall.Close(fd)
527
- return nil, wrapErr(ErrSocket, "bind: "+err.Error())
528
- }
529
-
530
- backlog := config.Backlog
531
- if backlog <= 0 {
532
- backlog = defaultBacklog
533
- }
534
-
535
- if err := syscall.Listen(fd, backlog); err != nil {
536
- syscall.Close(fd)
537
- os.Remove(path)
538
- return nil, wrapErr(ErrSocket, "listen: "+err.Error())
539
- }
540
-
541
- return &Listener{
542
- fd: fd,
543
- config: config,
544
- path: path,
545
- }, nil
546
-}
547
-
548
-// Fd returns the raw file descriptor for poll/epoll integration.
549
-func (l *Listener) Fd() int {
550
- return l.fd
551
-}
552
-
553
-// SetPayloadLimits updates the payload limits used for future handshakes.
554
-func (l *Listener) SetPayloadLimits(maxRequestPayloadBytes, maxResponsePayloadBytes uint32) {
555
- l.config.MaxRequestPayloadBytes = maxRequestPayloadBytes
556
- l.config.MaxResponsePayloadBytes = maxResponsePayloadBytes
557
-}
558
-
559
-// Accept accepts one client connection. Performs the full handshake.
560
-// Blocks until a client connects and the handshake completes.
561
-func (l *Listener) Accept() (*Session, error) {
562
- sessionID := l.nextSessionID.Add(1)
563
- return l.AcceptWithConfig(sessionID, l.config)
564
-}
565
-
566
-// AcceptWithConfig accepts one client connection using a caller-provided
567
-// per-session server config and session ID.
568
-func (l *Listener) AcceptWithConfig(sessionID uint64, config ServerConfig) (*Session, error) {
569
- nfd, _, err := syscall.Accept(l.fd)
570
- if err != nil {
571
- return nil, wrapErr(ErrAccept, err.Error())
572
- }
573
-
574
- session, herr := serverHandshake(nfd, &config, sessionID)
575
- if herr != nil {
576
- syscall.Close(nfd)
577
- return nil, herr
578
- }
579
- return session, nil
580
-}
581
-
582
-// Close closes the listener, stops accepting, and unlinks the socket file.
583
-func (l *Listener) Close() {
584
- if l.fd >= 0 {
585
- syscall.Close(l.fd)
586
- l.fd = -1
587
- }
588
- if l.path != "" {
589
- os.Remove(l.path)
590
- l.path = ""
591
- }
592
-}
593
-
65
// ---------------------------------------------------------------------------
66
// Internal helpers
67
// ---------------------------------------------------------------------------
@@ -664,13 +135,6 @@ func minU32(a, b uint32) uint32 {
135
return b
136
}
137
667
-func maxU32(a, b uint32) uint32 {
668
- if a > b {
669
- return a
670
- }
671
- return b
672
-}
673
-
138
// ---------------------------------------------------------------------------
139
// Low-level I/O
140
// ---------------------------------------------------------------------------
@@ -678,12 +142,12 @@ func maxU32(a, b uint32) uint32 {
142
// rawSendIov sends header + payload as one SEQPACKET message using sendmsg.
143
func rawSendIov(fd int, hdr []byte, payload []byte) error {
144
var iov [2]syscall.Iovec
681
- iov[0].Base = unsafe.SliceData(hdr)
145
+ iov[0].Base = unsafe.SliceData(hdr) // #nosec G103 -- sendmsg iovec needs the backing byte-slice pointer.
146
iov[0].SetLen(len(hdr))
147
148
iovlen := uint64(1)
149
if len(payload) > 0 {
686
- iov[1].Base = unsafe.SliceData(payload)
150
+ iov[1].Base = unsafe.SliceData(payload) // #nosec G103 -- sendmsg iovec needs the backing byte-slice pointer.
151
iov[1].SetLen(len(payload))
152
iovlen = 2
153
}
@@ -696,7 +160,7 @@ func rawSendIov(fd int, hdr []byte, payload []byte) error {
160
n, _, errno := syscall.Syscall(
161
syscall.SYS_SENDMSG,
162
uintptr(fd),
699
- uintptr(unsafe.Pointer(&msg)),
163
+ uintptr(unsafe.Pointer(&msg)), // #nosec G103 -- raw sendmsg syscall requires a Msghdr pointer.
164
uintptr(syscall.MSG_NOSIGNAL),
165
)
166
if errno != 0 {
@@ -728,346 +192,3 @@ func rawRecv(fd int, buf []byte) (int, error) {
192
}
193
return n, nil
194
}
731
-
732
-// ---------------------------------------------------------------------------
733
-// Stale endpoint recovery
734
-// ---------------------------------------------------------------------------
735
-
736
-type staleResult int
737
-
738
-const (
739
- staleNotExist staleResult = 0
740
- staleRecovered staleResult = 1
741
- staleLiveServer staleResult = 2
742
-)
743
-
744
-func checkAndRecoverStale(path string) staleResult {
745
- _, err := os.Stat(path)
746
- if err != nil {
747
- return staleNotExist
748
- }
749
-
750
- // Try connecting to see if a live server is there.
751
- // We use net.Dial instead of raw syscalls for the probe — it handles
752
- // all the sockaddr setup and is fine for a one-shot connectivity test.
753
- conn, err := net.Dial("unixpacket", path)
754
- if err == nil {
755
- // Connected => live server
756
- conn.Close()
757
- return staleLiveServer
758
- }
759
-
760
- // Only unlink on connection-refused (stale socket).
761
- // Other errors (EACCES, etc.) should not remove the file.
762
- if errors.Is(err, syscall.ECONNREFUSED) || errors.Is(err, syscall.ENOENT) {
763
- os.Remove(path)
764
- return staleRecovered
765
- }
766
- // Can't determine ownership — treat as live to prevent overwriting
767
- return staleLiveServer
768
-}
769
-
770
-// ---------------------------------------------------------------------------
771
-// Handshake: client side
772
-// ---------------------------------------------------------------------------
773
-
774
-func connectAndHandshake(fd int, path string, config *ClientConfig) (*Session, error) {
775
- // Connect
776
- sa := &syscall.SockaddrUnix{Name: path}
777
- if err := syscall.Connect(fd, sa); err != nil {
778
- return nil, wrapErr(ErrConnect, err.Error())
779
- }
780
-
781
- pktSize := config.PacketSize
782
- if pktSize == 0 {
783
- pktSize = detectPacketSize(fd)
784
- }
785
-
786
- supported := config.SupportedProfiles
787
- if supported == 0 {
788
- supported = protocol.ProfileBaseline
789
- }
790
-
791
- // Build HELLO
792
- hello := protocol.Hello{
793
- LayoutVersion: 1,
794
- Flags: 0,
795
- SupportedProfiles: supported,
796
- PreferredProfiles: config.PreferredProfiles,
797
- MaxRequestPayloadBytes: applyDefault(config.MaxRequestPayloadBytes, protocol.MaxPayloadDefault),
798
- MaxRequestBatchItems: applyDefault(config.MaxRequestBatchItems, defaultBatchItems),
799
- MaxResponsePayloadBytes: applyDefault(config.MaxResponsePayloadBytes, protocol.MaxPayloadDefault),
800
- MaxResponseBatchItems: applyDefault(config.MaxResponseBatchItems, defaultBatchItems),
801
- AuthToken: config.AuthToken,
802
- PacketSize: pktSize,
803
- }
804
-
805
- var helloBuf [helloPayloadSize]byte
806
- hello.Encode(helloBuf[:])
807
-
808
- // Build outer CONTROL header
809
- hdr := protocol.Header{
810
- Magic: protocol.MagicMsg,
811
- Version: protocol.Version,
812
- HeaderLen: protocol.HeaderLen,
813
- Kind: protocol.KindControl,
814
- Flags: 0,
815
- Code: protocol.CodeHello,
816
- TransportStatus: protocol.StatusOK,
817
- PayloadLen: helloPayloadSize,
818
- ItemCount: 1,
819
- MessageID: 0,
820
- }
821
-
822
- var pkt [protocol.HeaderSize + helloPayloadSize]byte
823
- hdr.Encode(pkt[:protocol.HeaderSize])
824
- copy(pkt[protocol.HeaderSize:], helloBuf[:])
825
-
826
- // Send HELLO
827
- n, err := syscall.SendmsgN(fd, pkt[:], nil, nil, 0)
828
- if err != nil {
829
- return nil, wrapErr(ErrSend, "hello send: "+err.Error())
830
- }
831
- if n != len(pkt) {
832
- return nil, wrapErr(ErrSend, "hello short write")
833
- }
834
-
835
- // Receive HELLO_ACK
836
- var ackBuf [128]byte
837
- an, _, _, _, err := syscall.Recvmsg(fd, ackBuf[:], nil, 0)
838
- if err != nil {
839
- return nil, wrapErr(ErrRecv, "hello_ack recv: "+err.Error())
840
- }
841
- if an == 0 {
842
- return nil, wrapErr(ErrRecv, "peer disconnected during handshake")
843
- }
844
-
845
- // Decode outer header
846
- ackHdr, err := protocol.DecodeHeader(ackBuf[:an])
847
- if err != nil {
848
- if errors.Is(err, protocol.ErrBadVersion) {
849
- return nil, wrapErr(ErrIncompatible, "ack header version mismatch")
850
- }
851
- return nil, wrapErr(ErrProtocol, "ack header: "+err.Error())
852
- }
853
-
854
- if ackHdr.Kind != protocol.KindControl || ackHdr.Code != protocol.CodeHelloAck {
855
- return nil, wrapErr(ErrProtocol, "expected HELLO_ACK")
856
- }
857
-
858
- // Check transport_status for rejection
859
- if ackHdr.TransportStatus == protocol.StatusAuthFailed {
860
- return nil, ErrAuthFailed
861
- }
862
- if ackHdr.TransportStatus == protocol.StatusUnsupported {
863
- return nil, ErrNoProfile
864
- }
865
- if ackHdr.TransportStatus == protocol.StatusIncompatible {
866
- return nil, ErrIncompatible
867
- }
868
- if ackHdr.TransportStatus == protocol.StatusLimitExceeded {
869
- return nil, ErrLimitExceeded
870
- }
871
- if ackHdr.TransportStatus != protocol.StatusOK {
872
- return nil, wrapErr(ErrHandshake, fmt.Sprintf("transport_status=%d", ackHdr.TransportStatus))
873
- }
874
-
875
- // Decode hello-ack payload
876
- if an < protocol.HeaderSize+helloAckPayloadSize {
877
- return nil, wrapErr(ErrProtocol, "ack payload truncated")
878
- }
879
- ack, err := protocol.DecodeHelloAck(ackBuf[protocol.HeaderSize:an])
880
- if err != nil {
881
- if errors.Is(err, protocol.ErrBadLayout) &&
882
- helloAckLayoutIncompatible(ackBuf[protocol.HeaderSize:an]) {
883
- return nil, wrapErr(ErrIncompatible, "ack payload layout version mismatch")
884
- }
885
- return nil, wrapErr(ErrProtocol, "ack payload: "+err.Error())
886
- }
887
-
888
- return &Session{
889
- fd: fd,
890
- role: RoleClient,
891
- MaxRequestPayloadBytes: ack.AgreedMaxRequestPayloadBytes,
892
- MaxRequestBatchItems: ack.AgreedMaxRequestBatchItems,
893
- MaxResponsePayloadBytes: ack.AgreedMaxResponsePayloadBytes,
894
- MaxResponseBatchItems: ack.AgreedMaxResponseBatchItems,
895
- PacketSize: ack.AgreedPacketSize,
896
- SelectedProfile: ack.SelectedProfile,
897
- SessionID: ack.SessionID,
898
- inflightIDs: make(map[uint64]struct{}),
899
- }, nil
900
-}
901
-
902
-// ---------------------------------------------------------------------------
903
-// Handshake: server side
904
-// ---------------------------------------------------------------------------
905
-
906
-func serverHandshake(fd int, config *ServerConfig, sessionID uint64) (*Session, error) {
907
- serverPktSize := config.PacketSize
908
- if serverPktSize == 0 {
909
- serverPktSize = detectPacketSize(fd)
910
- }
911
-
912
- sRespPay := applyDefault(config.MaxResponsePayloadBytes, protocol.MaxPayloadDefault)
913
- sProfiles := config.SupportedProfiles
914
- if sProfiles == 0 {
915
- sProfiles = protocol.ProfileBaseline
916
- }
917
- sPreferred := config.PreferredProfiles
918
-
919
- // Helper: send rejection ACK
920
- sendRejection := func(status uint16) {
921
- ack := protocol.HelloAck{LayoutVersion: 1}
922
- var ackPayBuf [helloAckPayloadSize]byte
923
- ack.Encode(ackPayBuf[:])
924
-
925
- ackHdr := protocol.Header{
926
- Magic: protocol.MagicMsg,
927
- Version: protocol.Version,
928
- HeaderLen: protocol.HeaderLen,
929
- Kind: protocol.KindControl,
930
- Code: protocol.CodeHelloAck,
931
- TransportStatus: status,
932
- PayloadLen: helloAckPayloadSize,
933
- ItemCount: 1,
934
- }
935
-
936
- var pkt [protocol.HeaderSize + helloAckPayloadSize]byte
937
- ackHdr.Encode(pkt[:protocol.HeaderSize])
938
- copy(pkt[protocol.HeaderSize:], ackPayBuf[:])
939
- // Best effort send
940
- syscall.SendmsgN(fd, pkt[:], nil, nil, 0) //nolint:errcheck
941
- }
942
-
943
- // Receive HELLO
944
- var buf [128]byte
945
- n, _, _, _, err := syscall.Recvmsg(fd, buf[:], nil, 0)
946
- if err != nil {
947
- return nil, wrapErr(ErrRecv, "hello recv: "+err.Error())
948
- }
949
- if n == 0 {
950
- return nil, wrapErr(ErrRecv, "peer disconnected during handshake")
951
- }
952
-
953
- hdr, err := protocol.DecodeHeader(buf[:n])
954
- if err != nil {
955
- if errors.Is(err, protocol.ErrBadVersion) &&
956
- headerVersionIncompatible(buf[:n], protocol.CodeHello) {
957
- sendRejection(protocol.StatusIncompatible)
958
- return nil, ErrIncompatible
959
- }
960
- return nil, wrapErr(ErrProtocol, "hello header: "+err.Error())
961
- }
962
-
963
- if hdr.Kind != protocol.KindControl || hdr.Code != protocol.CodeHello {
964
- return nil, wrapErr(ErrProtocol, "expected HELLO")
965
- }
966
-
967
- hello, err := protocol.DecodeHello(buf[protocol.HeaderSize:n])
968
- if err != nil {
969
- if errors.Is(err, protocol.ErrBadLayout) &&
970
- helloLayoutIncompatible(buf[protocol.HeaderSize:n]) {
971
- sendRejection(protocol.StatusIncompatible)
972
- return nil, ErrIncompatible
973
- }
974
- return nil, wrapErr(ErrProtocol, "hello payload: "+err.Error())
975
- }
976
-
977
- // Compute intersection
978
- intersection := hello.SupportedProfiles & sProfiles
979
-
980
- // Check intersection
981
- if intersection == 0 {
982
- sendRejection(protocol.StatusUnsupported)
983
- return nil, ErrNoProfile
984
- }
985
-
986
- // Check auth
987
- if hello.AuthToken != config.AuthToken {
988
- sendRejection(protocol.StatusAuthFailed)
989
- return nil, ErrAuthFailed
990
- }
991
-
992
- // Select profile: prefer preferred_intersection, then intersection
993
- preferredIntersection := intersection & hello.PreferredProfiles & sPreferred
994
- var selected uint32
995
- if preferredIntersection != 0 {
996
- selected = highestBit(preferredIntersection)
997
- } else {
998
- selected = highestBit(intersection)
999
- }
1000
-
1001
- if hello.MaxRequestPayloadBytes > protocol.MaxPayloadCap {
1002
- sendRejection(protocol.StatusLimitExceeded)
1003
- return nil, ErrLimitExceeded
1004
- }
1005
-
1006
- // Negotiate limits:
1007
- // - request payload and batch size are client-proposed and echoed
1008
- // - response payload is server-authoritative
1009
- // - response batch size is symmetric with request batch size
1010
- agreedReqPay := hello.MaxRequestPayloadBytes
1011
- agreedReqBat := hello.MaxRequestBatchItems
1012
- agreedRespPay := sRespPay
1013
- agreedRespBat := agreedReqBat
1014
- agreedPkt := minU32(hello.PacketSize, serverPktSize)
1015
- if agreedPkt <= protocol.HeaderSize {
1016
- sendRejection(protocol.StatusIncompatible)
1017
- return nil, ErrIncompatible
1018
- }
1019
-
1020
- // Send HELLO_ACK (success)
1021
- ack := protocol.HelloAck{
1022
- LayoutVersion: 1,
1023
- Flags: 0,
1024
- ServerSupportedProfiles: sProfiles,
1025
- IntersectionProfiles: intersection,
1026
- SelectedProfile: selected,
1027
- AgreedMaxRequestPayloadBytes: agreedReqPay,
1028
- AgreedMaxRequestBatchItems: agreedReqBat,
1029
- AgreedMaxResponsePayloadBytes: agreedRespPay,
1030
- AgreedMaxResponseBatchItems: agreedRespBat,
1031
- AgreedPacketSize: agreedPkt,
1032
- SessionID: sessionID,
1033
- }
1034
-
1035
- var ackPayBuf [helloAckPayloadSize]byte
1036
- ack.Encode(ackPayBuf[:])
1037
-
1038
- ackHdr := protocol.Header{
1039
- Magic: protocol.MagicMsg,
1040
- Version: protocol.Version,
1041
- HeaderLen: protocol.HeaderLen,
1042
- Kind: protocol.KindControl,
1043
- Code: protocol.CodeHelloAck,
1044
- TransportStatus: protocol.StatusOK,
1045
- PayloadLen: helloAckPayloadSize,
1046
- ItemCount: 1,
1047
- }
1048
-
1049
- var pkt [protocol.HeaderSize + helloAckPayloadSize]byte
1050
- ackHdr.Encode(pkt[:protocol.HeaderSize])
1051
- copy(pkt[protocol.HeaderSize:], ackPayBuf[:])
1052
-
1053
- sn, err := syscall.SendmsgN(fd, pkt[:], nil, nil, 0)
1054
- if err != nil {
1055
- return nil, wrapErr(ErrSend, "hello_ack send: "+err.Error())
1056
- }
1057
- if sn != len(pkt) {
1058
- return nil, wrapErr(ErrSend, "hello_ack short write")
1059
- }
1060
-
1061
- return &Session{
1062
- fd: fd,
1063
- role: RoleServer,
1064
- MaxRequestPayloadBytes: agreedReqPay,
1065
- MaxRequestBatchItems: agreedReqBat,
1066
- MaxResponsePayloadBytes: agreedRespPay,
1067
- MaxResponseBatchItems: agreedRespBat,
1068
- PacketSize: agreedPkt,
1069
- SelectedProfile: selected,
1070
- SessionID: sessionID,
1071
- inflightIDs: make(map[uint64]struct{}),
1072
- }, nil
1073
-}
src/go/pkg/netipc/transport/posix/uds_handshake.go
new
+156
@@ -0,0 +1,156 @@
1
+//go:build unix
2
+
3
+package posix
4
+
5
+import (
6
+ "fmt"
7
+ "syscall"
8
+
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
10
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/internal/framing"
11
+)
12
+
13
+func headerVersionIncompatible(buf []byte, expectedCode uint16) bool {
14
+ return framing.HeaderVersionIncompatible(buf, expectedCode)
15
+}
16
+
17
+func helloLayoutIncompatible(buf []byte) bool {
18
+ return framing.HelloLayoutIncompatible(buf)
19
+}
20
+
21
+func helloAckLayoutIncompatible(buf []byte) bool {
22
+ return framing.HelloAckLayoutIncompatible(buf)
23
+}
24
+
25
+func connectAndHandshake(fd int, path string, config *ClientConfig) (*Session, error) {
26
+ sa := &syscall.SockaddrUnix{Name: path}
27
+ if err := syscall.Connect(fd, sa); err != nil {
28
+ return nil, wrapErr(ErrConnect, err.Error())
29
+ }
30
+
31
+ pktSize := config.PacketSize
32
+ if pktSize == 0 {
33
+ pktSize = detectPacketSize(fd)
34
+ }
35
+
36
+ ack, err := framing.ClientHandshake(framing.ClientHandshakeConfig{
37
+ Hello: framing.HelloConfig{
38
+ SupportedProfiles: config.SupportedProfiles,
39
+ PreferredProfiles: config.PreferredProfiles,
40
+ MaxRequestPayloadBytes: applyDefault(config.MaxRequestPayloadBytes, protocol.MaxPayloadDefault),
41
+ MaxRequestBatchItems: applyDefault(config.MaxRequestBatchItems, defaultBatchItems),
42
+ MaxResponsePayloadBytes: applyDefault(config.MaxResponsePayloadBytes, protocol.MaxPayloadDefault),
43
+ MaxResponseBatchItems: applyDefault(config.MaxResponseBatchItems, defaultBatchItems),
44
+ AuthToken: config.AuthToken,
45
+ PacketSize: pktSize,
46
+ },
47
+ Send: func(pkt []byte) error {
48
+ n, err := syscall.SendmsgN(fd, pkt, nil, nil, 0)
49
+ if err != nil {
50
+ return err
51
+ }
52
+ if n != len(pkt) {
53
+ return fmt.Errorf("short write")
54
+ }
55
+ return nil
56
+ },
57
+ Recv: func(dst []byte) (int, error) { return rawRecv(fd, dst) },
58
+ StatusError: helloAckStatusError,
59
+ ErrSend: func(msg string) error { return wrapErr(ErrSend, msg) },
60
+ ErrRecv: func(msg string) error { return wrapErr(ErrRecv, msg) },
61
+ ErrProtocol: func(msg string) error { return wrapErr(ErrProtocol, msg) },
62
+ ErrIncompatible: func(msg string) error { return wrapErr(ErrIncompatible, msg) },
63
+ })
64
+ if err != nil {
65
+ return nil, err
66
+ }
67
+
68
+ return &Session{
69
+ fd: fd,
70
+ role: RoleClient,
71
+ MaxRequestPayloadBytes: ack.AgreedMaxRequestPayloadBytes,
72
+ MaxRequestBatchItems: ack.AgreedMaxRequestBatchItems,
73
+ MaxResponsePayloadBytes: ack.AgreedMaxResponsePayloadBytes,
74
+ MaxResponseBatchItems: ack.AgreedMaxResponseBatchItems,
75
+ PacketSize: ack.AgreedPacketSize,
76
+ SelectedProfile: ack.SelectedProfile,
77
+ SessionID: ack.SessionID,
78
+ inflightIDs: make(map[uint64]struct{}),
79
+ }, nil
80
+}
81
+
82
+func helloAckStatusError(status uint16) error {
83
+ switch status {
84
+ case protocol.StatusOK:
85
+ return nil
86
+ case protocol.StatusAuthFailed:
87
+ return ErrAuthFailed
88
+ case protocol.StatusUnsupported:
89
+ return ErrNoProfile
90
+ case protocol.StatusIncompatible:
91
+ return ErrIncompatible
92
+ case protocol.StatusLimitExceeded:
93
+ return ErrLimitExceeded
94
+ default:
95
+ return wrapErr(ErrHandshake, fmt.Sprintf("transport_status=%d", status))
96
+ }
97
+}
98
+
99
+func serverHandshake(fd int, config *ServerConfig, sessionID uint64) (*Session, error) {
100
+ serverPktSize := config.PacketSize
101
+ if serverPktSize == 0 {
102
+ serverPktSize = detectPacketSize(fd)
103
+ }
104
+
105
+ sRespPay := applyDefault(config.MaxResponsePayloadBytes, protocol.MaxPayloadDefault)
106
+ ack, err := framing.ServerHandshake(framing.ServerHandshakeConfig{
107
+ ServerHelloConfig: framing.ServerHelloConfig{
108
+ PacketSize: serverPktSize,
109
+ MaxResponsePayloadBytes: sRespPay,
110
+ SupportedProfiles: config.SupportedProfiles,
111
+ PreferredProfiles: config.PreferredProfiles,
112
+ AuthToken: config.AuthToken,
113
+ },
114
+ SessionID: sessionID,
115
+ Recv: func(dst []byte) (int, error) { return rawRecv(fd, dst) },
116
+ SendAck: func(status uint16, ack protocol.HelloAck) error { return sendHelloAck(fd, status, ack) },
117
+ StatusError: helloAckStatusError,
118
+ ErrRecv: func(msg string) error { return wrapErr(ErrRecv, msg) },
119
+ ErrSend: func(msg string) error { return wrapErr(ErrSend, msg) },
120
+ ErrProtocol: func(msg string) error { return wrapErr(ErrProtocol, msg) },
121
+ ErrIncompatible: func(msg string) error { return wrapErr(ErrIncompatible, msg) },
122
+ })
123
+ if err != nil {
124
+ return nil, err
125
+ }
126
+
127
+ return &Session{
128
+ fd: fd,
129
+ role: RoleServer,
130
+ MaxRequestPayloadBytes: ack.AgreedMaxRequestPayloadBytes,
131
+ MaxRequestBatchItems: ack.AgreedMaxRequestBatchItems,
132
+ MaxResponsePayloadBytes: ack.AgreedMaxResponsePayloadBytes,
133
+ MaxResponseBatchItems: ack.AgreedMaxResponseBatchItems,
134
+ PacketSize: ack.AgreedPacketSize,
135
+ SelectedProfile: ack.SelectedProfile,
136
+ SessionID: sessionID,
137
+ inflightIDs: make(map[uint64]struct{}),
138
+ }, nil
139
+}
140
+
141
+func sendRejection(fd int, status uint16) {
142
+ _ = sendHelloAck(fd, status, protocol.HelloAck{LayoutVersion: 1})
143
+}
144
+
145
+func sendHelloAck(fd int, status uint16, ack protocol.HelloAck) error {
146
+ pkt := framing.BuildHelloAckPacket(status, ack)
147
+
148
+ sn, err := syscall.SendmsgN(fd, pkt[:], nil, nil, 0)
149
+ if err != nil {
150
+ return err
151
+ }
152
+ if sn != len(pkt) {
153
+ return wrapErr(ErrSend, "hello_ack short write")
154
+ }
155
+ return nil
156
+}
src/go/pkg/netipc/transport/posix/uds_listener.go
new
+105
@@ -0,0 +1,105 @@
1
+//go:build unix
2
+
3
+package posix
4
+
5
+import (
6
+ "os"
7
+ "sync/atomic"
8
+ "syscall"
9
+)
10
+
11
+// Listener is a listening UDS SEQPACKET endpoint.
12
+type Listener struct {
13
+ fd int
14
+ config ServerConfig
15
+ path string
16
+ nextSessionID atomic.Uint64
17
+}
18
+
19
+// Listen creates a listener on {runDir}/{serviceName}.sock.
20
+// Performs stale endpoint recovery.
21
+func Listen(runDir, serviceName string, config ServerConfig) (*Listener, error) {
22
+ path, err := buildSocketPath(runDir, serviceName)
23
+ if err != nil {
24
+ return nil, err
25
+ }
26
+
27
+ stale := checkAndRecoverStale(path, runDirAllowsStaleUnlink(runDir))
28
+ if stale == staleLiveServer {
29
+ return nil, ErrAddrInUse
30
+ }
31
+
32
+ fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
33
+ if err != nil {
34
+ return nil, wrapErr(ErrSocket, err.Error())
35
+ }
36
+
37
+ sa := &syscall.SockaddrUnix{Name: path}
38
+ if err := syscall.Bind(fd, sa); err != nil {
39
+ _ = syscall.Close(fd)
40
+ return nil, wrapErr(ErrSocket, "bind: "+err.Error())
41
+ }
42
+
43
+ backlog := config.Backlog
44
+ if backlog <= 0 {
45
+ backlog = defaultBacklog
46
+ }
47
+
48
+ if err := syscall.Listen(fd, backlog); err != nil {
49
+ _ = syscall.Close(fd)
50
+ _ = os.Remove(path)
51
+ return nil, wrapErr(ErrSocket, "listen: "+err.Error())
52
+ }
53
+
54
+ return &Listener{
55
+ fd: fd,
56
+ config: config,
57
+ path: path,
58
+ }, nil
59
+}
60
+
61
+// Fd returns the raw file descriptor for poll/epoll integration.
62
+func (l *Listener) Fd() int {
63
+ return l.fd
64
+}
65
+
66
+// SetPayloadLimits updates the payload limits used for future handshakes.
67
+func (l *Listener) SetPayloadLimits(maxRequestPayloadBytes, maxResponsePayloadBytes uint32) {
68
+ l.config.MaxRequestPayloadBytes = maxRequestPayloadBytes
69
+ l.config.MaxResponsePayloadBytes = maxResponsePayloadBytes
70
+}
71
+
72
+// Accept accepts one client connection. Performs the full handshake.
73
+// Blocks until a client connects and the handshake completes.
74
+func (l *Listener) Accept() (*Session, error) {
75
+ sessionID := l.nextSessionID.Add(1)
76
+ return l.AcceptWithConfig(sessionID, l.config)
77
+}
78
+
79
+// AcceptWithConfig accepts one client connection using a caller-provided
80
+// per-session server config and session ID.
81
+func (l *Listener) AcceptWithConfig(sessionID uint64, config ServerConfig) (*Session, error) {
82
+ nfd, _, err := syscall.Accept(l.fd)
83
+ if err != nil {
84
+ return nil, wrapErr(ErrAccept, err.Error())
85
+ }
86
+
87
+ session, herr := serverHandshake(nfd, &config, sessionID)
88
+ if herr != nil {
89
+ _ = syscall.Close(nfd)
90
+ return nil, herr
91
+ }
92
+ return session, nil
93
+}
94
+
95
+// Close closes the listener, stops accepting, and unlinks the socket file.
96
+func (l *Listener) Close() {
97
+ if l.fd >= 0 {
98
+ _ = syscall.Close(l.fd)
99
+ l.fd = -1
100
+ }
101
+ if l.path != "" {
102
+ _ = os.Remove(l.path)
103
+ l.path = ""
104
+ }
105
+}
src/go/pkg/netipc/transport/posix/uds_receive.go
new
+41
@@ -0,0 +1,41 @@
1
+//go:build unix
2
+
3
+package posix
4
+
5
+import (
6
+ "errors"
7
+
8
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/internal/framing"
10
+)
11
+
12
+// Receive reads one logical message. Blocks until a complete message
13
+// arrives. buf is a caller-provided scratch buffer for the first packet.
14
+// On success, returns the header and a payload view valid until the next
15
+// Receive call on this session.
16
+func (s *Session) Receive(buf []byte) (protocol.Header, []byte, error) {
17
+ if s.fd < 0 {
18
+ return protocol.Header{}, nil, wrapErr(ErrBadParam, "session closed")
19
+ }
20
+
21
+ return framing.SessionReceive(framing.SessionReceiveConfig{
22
+ RoleServer: s.role == RoleServer,
23
+ PacketSize: s.PacketSize,
24
+ MaxRequestPayloadBytes: s.MaxRequestPayloadBytes,
25
+ MaxRequestBatchItems: s.MaxRequestBatchItems,
26
+ MaxResponsePayloadBytes: s.MaxResponsePayloadBytes,
27
+ MaxResponseBatchItems: s.MaxResponseBatchItems,
28
+ InflightIDs: s.inflightIDs,
29
+ RecvBuf: &s.recvBuf,
30
+ PacketBuf: &s.pktBuf,
31
+ Recv: func(dst []byte) (int, error) { return rawRecv(s.fd, dst) },
32
+ EnsurePacketScratch: ensureScratchBuf,
33
+ IsRecvDisconnect: func(err error) bool { return errors.Is(err, ErrRecv) },
34
+ FailAllInflight: s.failAllInflight,
35
+ ErrLimitExceeded: func(msg string) error { return wrapErr(ErrLimitExceeded, msg) },
36
+ ErrProtocol: func(msg string) error { return wrapErr(ErrProtocol, msg) },
37
+ ErrChunk: func(msg string) error { return wrapErr(ErrChunk, msg) },
38
+ ErrUnknownMsgID: func(msg string) error { return wrapErr(ErrUnknownMsgID, msg) },
39
+ ErrRecv: func(msg string) error { return wrapErr(ErrRecv, msg) },
40
+ }, buf)
41
+}
src/go/pkg/netipc/transport/posix/uds_send.go
new
+41
@@ -0,0 +1,41 @@
1
+//go:build unix
2
+
3
+package posix
4
+
5
+import (
6
+ "errors"
7
+
8
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/internal/framing"
10
+)
11
+
12
+// Send sends one logical message. The caller fills Kind, Code, Flags,
13
+// ItemCount, MessageID in hdr; this function sets Magic/Version/
14
+// HeaderLen/PayloadLen. If the total message exceeds PacketSize,
15
+// it is chunked transparently.
16
+func (s *Session) Send(hdr *protocol.Header, payload []byte) error {
17
+ if s.fd < 0 {
18
+ return wrapErr(ErrBadParam, "session closed")
19
+ }
20
+
21
+ return framing.SessionSend(framing.SessionSendConfig{
22
+ RoleClient: s.role == RoleClient,
23
+ PacketSize: s.PacketSize,
24
+ InflightIDs: &s.inflightIDs,
25
+ FailAllInflight: s.failAllInflight,
26
+ IsSendDisconnect: func(err error) bool { return errors.Is(err, ErrSend) },
27
+ SendFirstPacket: func(packetHdr *protocol.Header, packetPayload []byte, _ int) error {
28
+ var hdrBuf [protocol.HeaderSize]byte
29
+ packetHdr.Encode(hdrBuf[:])
30
+ return rawSendIov(s.fd, hdrBuf[:], packetPayload)
31
+ },
32
+ SendChunk: func(chk protocol.ChunkHeader, chunkPayload []byte) error {
33
+ var chkBuf [protocol.HeaderSize]byte
34
+ chk.Encode(chkBuf[:])
35
+ return rawSendIov(s.fd, chkBuf[:], chunkPayload)
36
+ },
37
+ ErrLimitExceeded: func(msg string) error { return wrapErr(ErrLimitExceeded, msg) },
38
+ ErrDuplicateMsgID: func(msg string) error { return wrapErr(ErrDuplicateMsgID, msg) },
39
+ ErrBadParam: func(msg string) error { return wrapErr(ErrBadParam, msg) },
40
+ }, hdr, payload)
41
+}
src/go/pkg/netipc/transport/posix/uds_send_test.go
new
+32
@@ -0,0 +1,32 @@
1
+//go:build unix
2
+
3
+package posix
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/internal/framing"
10
+)
11
+
12
+func TestHeaderPayloadLenBounds(t *testing.T) {
13
+ if _, _, ok := framing.HeaderPayloadLen(-1); ok {
14
+ t.Fatal("negative payload length should fail")
15
+ }
16
+
17
+ maxInt := int(^uint(0) >> 1)
18
+ if _, _, ok := framing.HeaderPayloadLen(maxInt); ok {
19
+ t.Fatal("payload length that overflows int total length should fail")
20
+ }
21
+
22
+ protocolLimit := uint64(^uint32(0)) - uint64(protocol.HeaderSize)
23
+ if protocolLimit <= uint64(maxInt) {
24
+ got, _, ok := framing.HeaderPayloadLen(int(protocolLimit))
25
+ if !ok {
26
+ t.Fatal("protocol-limit payload should pass")
27
+ }
28
+ if uint64(got) != uint64(^uint32(0)) {
29
+ t.Fatalf("total length = %d, want %d", got, uint64(^uint32(0)))
30
+ }
31
+ }
32
+}
src/go/pkg/netipc/transport/posix/uds_session.go
new
+106
@@ -0,0 +1,106 @@
1
+//go:build unix
2
+
3
+package posix
4
+
5
+import "syscall"
6
+
7
+// Role distinguishes client vs server sessions.
8
+type Role int
9
+
10
+const (
11
+ RoleClient Role = 1
12
+ RoleServer Role = 2
13
+)
14
+
15
+// ClientConfig configures a client connection.
16
+type ClientConfig struct {
17
+ SupportedProfiles uint32
18
+ PreferredProfiles uint32
19
+ MaxRequestPayloadBytes uint32 // 0 = use default (1024)
20
+ MaxRequestBatchItems uint32 // 0 = use default (1)
21
+ MaxResponsePayloadBytes uint32
22
+ MaxResponseBatchItems uint32
23
+ AuthToken uint64
24
+ PacketSize uint32 // 0 = auto-detect from SO_SNDBUF
25
+}
26
+
27
+// ServerConfig configures a listener and its accepted sessions.
28
+type ServerConfig struct {
29
+ SupportedProfiles uint32
30
+ PreferredProfiles uint32
31
+ MaxRequestPayloadBytes uint32
32
+ MaxRequestBatchItems uint32
33
+ MaxResponsePayloadBytes uint32
34
+ MaxResponseBatchItems uint32
35
+ AuthToken uint64
36
+ PacketSize uint32 // 0 = auto-detect from SO_SNDBUF
37
+ Backlog int // 0 = default (16)
38
+}
39
+
40
+// Session is a connected UDS SEQPACKET session (client or server side).
41
+type Session struct {
42
+ fd int
43
+ role Role
44
+
45
+ MaxRequestPayloadBytes uint32
46
+ MaxRequestBatchItems uint32
47
+ MaxResponsePayloadBytes uint32
48
+ MaxResponseBatchItems uint32
49
+ PacketSize uint32
50
+ SelectedProfile uint32
51
+ SessionID uint64
52
+
53
+ recvBuf []byte
54
+ pktBuf []byte
55
+
56
+ inflightIDs map[uint64]struct{}
57
+}
58
+
59
+func (s *Session) failAllInflight() {
60
+ if s.role != RoleClient || len(s.inflightIDs) == 0 {
61
+ return
62
+ }
63
+ clear(s.inflightIDs)
64
+}
65
+
66
+// Fd returns the raw file descriptor for poll/epoll integration.
67
+func (s *Session) Fd() int {
68
+ return s.fd
69
+}
70
+
71
+// Role returns the session role.
72
+func (s *Session) Role() Role {
73
+ return s.role
74
+}
75
+
76
+// Close closes the session and releases resources.
77
+func (s *Session) Close() {
78
+ if s.fd >= 0 {
79
+ _ = syscall.Close(s.fd)
80
+ s.fd = -1
81
+ }
82
+ s.recvBuf = nil
83
+ s.pktBuf = nil
84
+ s.failAllInflight()
85
+}
86
+
87
+// Connect establishes a session to a server at {runDir}/{serviceName}.sock.
88
+// Performs the full handshake. Blocks until connected + handshake done.
89
+func Connect(runDir, serviceName string, config *ClientConfig) (*Session, error) {
90
+ path, err := buildSocketPath(runDir, serviceName)
91
+ if err != nil {
92
+ return nil, err
93
+ }
94
+
95
+ fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_SEQPACKET, 0)
96
+ if err != nil {
97
+ return nil, wrapErr(ErrSocket, err.Error())
98
+ }
99
+
100
+ session, herr := connectAndHandshake(fd, path, config)
101
+ if herr != nil {
102
+ _ = syscall.Close(fd)
103
+ return nil, herr
104
+ }
105
+ return session, nil
106
+}
src/go/pkg/netipc/transport/posix/uds_stale.go
new
+86
@@ -0,0 +1,86 @@
1
+//go:build unix
2
+
3
+package posix
4
+
5
+import (
6
+ "errors"
7
+ "net"
8
+ "os"
9
+ "syscall"
10
+ "time"
11
+)
12
+
13
+type staleResult int
14
+
15
+const (
16
+ staleNotExist staleResult = 0
17
+ staleRecovered staleResult = 1
18
+ staleLiveServer staleResult = 2
19
+)
20
+
21
+const (
22
+ staleDialAttempts = 3
23
+ staleDialRetryDelay = 50 * time.Millisecond
24
+ staleDialTimeout = 1 * time.Second
25
+)
26
+
27
+func runDirAllowsStaleUnlink(runDir string) bool {
28
+ euid := uint32(os.Geteuid())
29
+ info, err := os.Stat(runDir)
30
+ if err != nil || !info.IsDir() {
31
+ return false
32
+ }
33
+ st, ok := info.Sys().(*syscall.Stat_t)
34
+ if !ok || st.Uid != euid {
35
+ return false
36
+ }
37
+ return info.Mode().Perm()&0022 == 0
38
+}
39
+
40
+func dialStaleCandidate(path string) error {
41
+ var err error
42
+ for attempt := range staleDialAttempts {
43
+ var conn net.Conn
44
+ conn, err = net.DialTimeout("unixpacket", path, staleDialTimeout)
45
+ if err == nil {
46
+ _ = conn.Close()
47
+ return nil
48
+ }
49
+ if !errors.Is(err, syscall.ECONNREFUSED) {
50
+ return err
51
+ }
52
+ if attempt+1 < staleDialAttempts {
53
+ time.Sleep(staleDialRetryDelay)
54
+ }
55
+ }
56
+ return err
57
+}
58
+
59
+func checkAndRecoverStale(path string, allowStaleUnlink bool) staleResult {
60
+ _, err := os.Stat(path)
61
+ if err != nil {
62
+ return staleNotExist
63
+ }
64
+
65
+ err = dialStaleCandidate(path)
66
+ if err == nil {
67
+ return staleLiveServer
68
+ }
69
+
70
+ if errors.Is(err, syscall.ENOENT) {
71
+ return staleNotExist
72
+ }
73
+ if errors.Is(err, syscall.ECONNREFUSED) {
74
+ if !allowStaleUnlink {
75
+ return staleLiveServer
76
+ }
77
+ if removeErr := os.Remove(path); removeErr != nil {
78
+ if os.IsNotExist(removeErr) {
79
+ return staleNotExist
80
+ }
81
+ return staleLiveServer
82
+ }
83
+ return staleRecovered
84
+ }
85
+ return staleLiveServer
86
+}
src/go/pkg/netipc/transport/posix/uds_test.go
+1
-1
@@ -467,7 +467,7 @@ func TestChunking(t *testing.T) {
467
}
468
469
// Client receives
470
- rHdr, rPayload, err = client.Receive(recvBuf)
470
+ _, rPayload, err = client.Receive(recvBuf)
471
if err != nil {
472
t.Fatalf("client Receive (chunked): %v", err)
473
}
src/go/pkg/netipc/transport/windows/pipe.go
-939
@@ -10,17 +10,11 @@
10
package windows
11
12
import (
13
- "encoding/binary"
13
"errors"
14
"fmt"
16
- "sync"
17
- "sync/atomic"
15
"syscall"
19
- "time"
16
"unicode/utf16"
17
"unsafe"
22
-
23
- "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
18
)
19
20
// ---------------------------------------------------------------------------
@@ -89,26 +83,6 @@ func wrapErr(sentinel error, detail string) error {
83
return fmt.Errorf("%w: %s", sentinel, detail)
84
}
85
92
-func headerVersionIncompatible(buf []byte, expectedCode uint16) bool {
93
- if len(buf) < protocol.HeaderSize {
94
- return false
95
- }
96
-
97
- return binary.NativeEndian.Uint32(buf[0:4]) == protocol.MagicMsg &&
98
- binary.NativeEndian.Uint16(buf[4:6]) != protocol.Version &&
99
- binary.NativeEndian.Uint16(buf[6:8]) == protocol.HeaderLen &&
100
- binary.NativeEndian.Uint16(buf[8:10]) == protocol.KindControl &&
101
- binary.NativeEndian.Uint16(buf[12:14]) == expectedCode
102
-}
103
-
104
-func helloLayoutIncompatible(buf []byte) bool {
105
- return len(buf) >= 2 && binary.NativeEndian.Uint16(buf[0:2]) != 1
106
-}
107
-
108
-func helloAckLayoutIncompatible(buf []byte) bool {
109
- return len(buf) >= 2 && binary.NativeEndian.Uint16(buf[0:2]) != 1
110
-}
111
-
86
// ---------------------------------------------------------------------------
87
// Win32 syscall imports (pure Go, no cgo)
88
// ---------------------------------------------------------------------------
@@ -297,46 +271,6 @@ func isDisconnectError(err error) bool {
271
errno == _ERROR_PIPE_NOT_CONNECTED
272
}
273
300
-// ---------------------------------------------------------------------------
301
-// Role
302
-// ---------------------------------------------------------------------------
303
-
304
-// Role distinguishes client vs server sessions.
305
-type Role int
306
-
307
-const (
308
- RoleClient Role = 1
309
- RoleServer Role = 2
310
-)
311
-
312
-// ---------------------------------------------------------------------------
313
-// Configuration
314
-// ---------------------------------------------------------------------------
315
-
316
-// ClientConfig configures a client connection.
317
-type ClientConfig struct {
318
- SupportedProfiles uint32
319
- PreferredProfiles uint32
320
- MaxRequestPayloadBytes uint32 // 0 = use default (1024)
321
- MaxRequestBatchItems uint32 // 0 = use default (1)
322
- MaxResponsePayloadBytes uint32
323
- MaxResponseBatchItems uint32
324
- AuthToken uint64
325
- PacketSize uint32 // 0 = use default (65536)
326
-}
327
-
328
-// ServerConfig configures a listener and its accepted sessions.
329
-type ServerConfig struct {
330
- SupportedProfiles uint32
331
- PreferredProfiles uint32
332
- MaxRequestPayloadBytes uint32
333
- MaxRequestBatchItems uint32
334
- MaxResponsePayloadBytes uint32
335
- MaxResponseBatchItems uint32
336
- AuthToken uint64
337
- PacketSize uint32 // 0 = use default (65536)
338
-}
339
-
274
// ---------------------------------------------------------------------------
275
// Low-level I/O
276
// ---------------------------------------------------------------------------
@@ -386,882 +320,9 @@ func rawRecv(handle syscall.Handle, buf []byte) (int, error) {
320
return int(read), nil
321
}
322
389
-// ---------------------------------------------------------------------------
390
-// Session
391
-// ---------------------------------------------------------------------------
392
-
393
-// Session is a connected Named Pipe session (client or server side).
394
-type Session struct {
395
- handle syscall.Handle
396
- role Role
397
-
398
- // Negotiated limits
399
- MaxRequestPayloadBytes uint32
400
- MaxRequestBatchItems uint32
401
- MaxResponsePayloadBytes uint32
402
- MaxResponseBatchItems uint32
403
- PacketSize uint32
404
- SelectedProfile uint32
405
- SessionID uint64
406
-
407
- // Internal receive buffer for chunked reassembly
408
- recvBuf []byte
409
-
410
- // Reusable packet scratch buffers for send/receive chunk assembly.
411
- sendBuf []byte
412
- pktBuf []byte
413
-
414
- // In-flight message_id set (client-side only)
415
- inflightIDs map[uint64]struct{}
416
-}
417
-
418
-func (s *Session) failAllInflight() {
419
- if s.role != RoleClient || len(s.inflightIDs) == 0 {
420
- return
421
- }
422
- clear(s.inflightIDs)
423
-}
424
-
425
-// Handle returns the raw HANDLE for WaitForSingleObject integration.
426
-func (s *Session) Handle() syscall.Handle {
427
- return s.handle
428
-}
429
-
430
-// Role returns the session role.
431
-func (s *Session) GetRole() Role {
432
- return s.role
433
-}
434
-
435
-// WaitReadable waits until bytes are available to read or the timeout expires.
436
-func (s *Session) WaitReadable(timeoutMs uint32) (bool, error) {
437
- if s.handle == syscall.InvalidHandle {
438
- return false, wrapErr(ErrBadParam, "session closed")
439
- }
440
-
441
- deadline := time.Now().Add(time.Duration(timeoutMs) * time.Millisecond)
442
- yielded := false
443
- for {
444
- available, err := peekNamedPipeAvailable(s.handle)
445
- if err != nil {
446
- if isDisconnectError(err) {
447
- s.failAllInflight()
448
- return false, ErrDisconnected
449
- }
450
- return false, wrapErr(ErrRecv, err.Error())
451
- }
452
- if available > 0 {
453
- return true, nil
454
- }
455
- if !time.Now().Before(deadline) {
456
- return false, nil
457
- }
458
- if !yielded {
459
- yielded = true
460
- for i := 0; i < 256; i++ {
461
- procSwitchToThread.Call()
462
- available, err = peekNamedPipeAvailable(s.handle)
463
- if err != nil {
464
- if isDisconnectError(err) {
465
- s.failAllInflight()
466
- return false, ErrDisconnected
467
- }
468
- return false, wrapErr(ErrRecv, err.Error())
469
- }
470
- if available > 0 {
471
- return true, nil
472
- }
473
- if !time.Now().Before(deadline) {
474
- return false, nil
475
- }
476
- }
477
- continue
478
- }
479
- time.Sleep(time.Millisecond)
480
- }
481
-}
482
-
483
-// Close closes the session and releases resources.
484
-func (s *Session) Close() {
485
- if s.handle != syscall.InvalidHandle {
486
- if s.role == RoleServer {
487
- // Flush before server-side disconnect so the client can
488
- // consume any final response bytes already written.
489
- flushFileBuffers(s.handle)
490
- disconnectNamedPipe(s.handle)
491
- }
492
- syscall.CloseHandle(s.handle)
493
- s.handle = syscall.InvalidHandle
494
- }
495
- s.recvBuf = nil
496
- s.sendBuf = nil
497
- s.pktBuf = nil
498
- s.failAllInflight()
499
-}
500
-
501
-// Connect establishes a session to a server pipe derived from runDir + serviceName.
502
-func Connect(runDir, serviceName string, config *ClientConfig) (*Session, error) {
503
- pipeName, err := BuildPipeName(runDir, serviceName)
504
- if err != nil {
505
- return nil, err
506
- }
507
-
508
- handle, err := syscall.CreateFile(
509
- &pipeName[0],
510
- _GENERIC_READ|_GENERIC_WRITE,
511
- 0,
512
- nil,
513
- _OPEN_EXISTING,
514
- 0,
515
- 0,
516
- )
517
- if err != nil {
518
- return nil, wrapErr(ErrConnect, err.Error())
519
- }
520
-
521
- // Set read mode to message mode
522
- mode := uint32(_PIPE_READMODE_MESSAGE)
523
- if err := setNamedPipeHandleState(handle, &mode); err != nil {
524
- syscall.CloseHandle(handle)
525
- return nil, wrapErr(ErrConnect, "SetNamedPipeHandleState: "+err.Error())
526
- }
527
-
528
- session, herr := clientHandshake(handle, config)
529
- if herr != nil {
530
- syscall.CloseHandle(handle)
531
- return nil, herr
532
- }
533
- return session, nil
534
-}
535
-
536
-// Send sends one logical message. Fills magic/version/header_len/payload_len.
537
-func (s *Session) Send(hdr *protocol.Header, payload []byte) error {
538
- if s.handle == syscall.InvalidHandle {
539
- return wrapErr(ErrBadParam, "session closed")
540
- }
541
-
542
- // Client-side: track in-flight message_ids
543
- if s.role == RoleClient && hdr.Kind == protocol.KindRequest {
544
- if s.inflightIDs == nil {
545
- s.inflightIDs = make(map[uint64]struct{})
546
- }
547
- if _, exists := s.inflightIDs[hdr.MessageID]; exists {
548
- return wrapErr(ErrDuplicateMsgID, fmt.Sprintf("message_id %d", hdr.MessageID))
549
- }
550
- s.inflightIDs[hdr.MessageID] = struct{}{}
551
- }
552
-
553
- // Fill envelope
554
- hdr.Magic = protocol.MagicMsg
555
- hdr.Version = protocol.Version
556
- hdr.HeaderLen = protocol.HeaderLen
557
- hdr.PayloadLen = uint32(len(payload))
558
-
559
- tracked := s.role == RoleClient && hdr.Kind == protocol.KindRequest
560
-
561
- sendErr := s.sendInner(hdr, payload)
562
-
563
- if sendErr != nil && tracked {
564
- if errors.Is(sendErr, ErrDisconnected) {
565
- s.failAllInflight()
566
- } else {
567
- delete(s.inflightIDs, hdr.MessageID)
568
- }
569
- }
570
-
571
- return sendErr
572
-}
573
-
574
-func (s *Session) sendInner(hdr *protocol.Header, payload []byte) error {
575
- totalMsg := protocol.HeaderSize + len(payload)
576
-
577
- // Single packet?
578
- if totalMsg <= int(s.PacketSize) {
579
- msg := ensurePipeScratchBuf(&s.sendBuf, totalMsg)
580
- hdr.Encode(msg[:protocol.HeaderSize])
581
- copy(msg[protocol.HeaderSize:], payload)
582
- return rawSendMsg(s.handle, msg[:totalMsg])
583
- }
584
-
585
- // Chunked send
586
- chunkPayloadBudget := int(s.PacketSize) - protocol.HeaderSize
587
- if chunkPayloadBudget <= 0 {
588
- return wrapErr(ErrBadParam, "packet_size too small")
589
- }
590
-
591
- firstChunkPayload := len(payload)
592
- if firstChunkPayload > chunkPayloadBudget {
593
- firstChunkPayload = chunkPayloadBudget
594
- }
595
-
596
- remainingAfterFirst := len(payload) - firstChunkPayload
597
- continuationChunks := uint32(0)
598
- if remainingAfterFirst > 0 {
599
- continuationChunks = uint32((remainingAfterFirst + chunkPayloadBudget - 1) / chunkPayloadBudget)
600
- }
601
- chunkCount := 1 + continuationChunks
602
-
603
- // First chunk
604
- pktBuf := ensurePipeScratchBuf(&s.sendBuf, int(s.PacketSize))
605
- hdr.Encode(pktBuf[:protocol.HeaderSize])
606
- copy(pktBuf[protocol.HeaderSize:], payload[:firstChunkPayload])
607
- if err := rawSendMsg(s.handle, pktBuf[:protocol.HeaderSize+firstChunkPayload]); err != nil {
608
- return err
609
- }
610
-
611
- // Continuation chunks
612
- offset := firstChunkPayload
613
- for ci := uint32(1); ci < chunkCount; ci++ {
614
- remaining := len(payload) - offset
615
- thisChunk := remaining
616
- if thisChunk > chunkPayloadBudget {
617
- thisChunk = chunkPayloadBudget
618
- }
619
-
620
- chk := protocol.ChunkHeader{
621
- Magic: protocol.MagicChunk,
622
- Version: protocol.Version,
623
- Flags: 0,
624
- MessageID: hdr.MessageID,
625
- TotalMessageLen: uint32(totalMsg),
626
- ChunkIndex: ci,
627
- ChunkCount: chunkCount,
628
- ChunkPayloadLen: uint32(thisChunk),
629
- }
630
-
631
- chk.Encode(pktBuf[:protocol.HeaderSize])
632
- copy(pktBuf[protocol.HeaderSize:], payload[offset:offset+thisChunk])
633
- if err := rawSendMsg(s.handle, pktBuf[:protocol.HeaderSize+thisChunk]); err != nil {
634
- return err
635
- }
636
-
637
- offset += thisChunk
638
- }
639
-
640
- return nil
641
-}
642
-
643
-// Receive reads one logical message. buf is a scratch buffer.
644
-func (s *Session) Receive(buf []byte) (protocol.Header, []byte, error) {
645
- if s.handle == syscall.InvalidHandle {
646
- return protocol.Header{}, nil, wrapErr(ErrBadParam, "session closed")
647
- }
648
-
649
- n, err := rawRecv(s.handle, buf)
650
- if err != nil {
651
- if errors.Is(err, ErrDisconnected) {
652
- s.failAllInflight()
653
- }
654
- return protocol.Header{}, nil, err
655
- }
656
-
657
- if n < protocol.HeaderSize {
658
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "packet too short for header")
659
- }
660
-
661
- hdr, err := protocol.DecodeHeader(buf[:n])
662
- if err != nil {
663
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "header decode: "+err.Error())
664
- }
665
-
666
- // Validate payload_len
667
- var maxPayload uint32
668
- if s.role == RoleServer {
669
- maxPayload = s.MaxRequestPayloadBytes
670
- } else {
671
- maxPayload = s.MaxResponsePayloadBytes
672
- }
673
- if hdr.PayloadLen > maxPayload {
674
- return protocol.Header{}, nil, wrapErr(ErrLimitExceeded,
675
- fmt.Sprintf("payload_len %d exceeds negotiated max %d", hdr.PayloadLen, maxPayload))
676
- }
677
-
678
- // Validate item_count
679
- var maxBatch uint32
680
- if s.role == RoleServer {
681
- maxBatch = s.MaxRequestBatchItems
682
- } else {
683
- maxBatch = s.MaxResponseBatchItems
684
- }
685
- if hdr.ItemCount > maxBatch {
686
- return protocol.Header{}, nil, wrapErr(ErrLimitExceeded,
687
- fmt.Sprintf("item_count %d exceeds negotiated max %d", hdr.ItemCount, maxBatch))
688
- }
689
-
690
- // Client-side: validate response message_id
691
- if s.role == RoleClient && hdr.Kind == protocol.KindResponse {
692
- if s.inflightIDs == nil {
693
- return protocol.Header{}, nil, wrapErr(ErrUnknownMsgID,
694
- fmt.Sprintf("message_id %d", hdr.MessageID))
695
- }
696
- if _, exists := s.inflightIDs[hdr.MessageID]; !exists {
697
- return protocol.Header{}, nil, wrapErr(ErrUnknownMsgID,
698
- fmt.Sprintf("message_id %d", hdr.MessageID))
699
- }
700
- delete(s.inflightIDs, hdr.MessageID)
701
- }
702
-
703
- totalMsg := protocol.HeaderSize + int(hdr.PayloadLen)
704
-
705
- // Non-chunked
706
- if n >= totalMsg {
707
- payload := buf[protocol.HeaderSize : protocol.HeaderSize+int(hdr.PayloadLen)]
708
-
709
- // Validate batch directory
710
- if hdr.Flags&protocol.FlagBatch != 0 && hdr.ItemCount > 1 {
711
- dirBytes := int(hdr.ItemCount) * 8
712
- dirAligned := protocol.Align8(dirBytes)
713
- if len(payload) < dirAligned {
714
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "batch dir exceeds payload")
715
- }
716
- packedAreaLen := uint32(len(payload) - dirAligned)
717
- if err := protocol.BatchDirValidate(payload[:dirBytes], hdr.ItemCount, packedAreaLen); err != nil {
718
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "batch dir: "+err.Error())
719
- }
720
- }
721
-
722
- return hdr, payload, nil
723
- }
724
-
725
- // Chunked
726
- firstPayloadBytes := n - protocol.HeaderSize
727
- needed := int(hdr.PayloadLen)
728
- if len(s.recvBuf) < needed {
729
- s.recvBuf = make([]byte, needed)
730
- }
731
-
732
- copy(s.recvBuf[:firstPayloadBytes], buf[protocol.HeaderSize:protocol.HeaderSize+firstPayloadBytes])
733
-
734
- assembled := firstPayloadBytes
735
- chunkPayloadBudget := int(s.PacketSize) - protocol.HeaderSize
736
-
737
- remainingAfterFirst := int(hdr.PayloadLen) - firstPayloadBytes
738
- expectedContinuations := uint32(0)
739
- if remainingAfterFirst > 0 && chunkPayloadBudget > 0 {
740
- expectedContinuations = uint32((remainingAfterFirst + chunkPayloadBudget - 1) / chunkPayloadBudget)
741
- }
742
- expectedChunkCount := 1 + expectedContinuations
743
-
744
- pktBuf := ensurePipeScratchBuf(&s.pktBuf, int(s.PacketSize))
745
-
746
- ci := uint32(1)
747
- for assembled < int(hdr.PayloadLen) {
748
- cn, err := rawRecv(s.handle, pktBuf)
749
- if err != nil {
750
- if errors.Is(err, ErrDisconnected) {
751
- s.failAllInflight()
752
- }
753
- return protocol.Header{}, nil, wrapErr(ErrRecv, "continuation recv: "+err.Error())
754
- }
755
-
756
- if cn < protocol.HeaderSize {
757
- return protocol.Header{}, nil, wrapErr(ErrChunk, "continuation too short")
758
- }
759
-
760
- chk, err := protocol.DecodeChunkHeader(pktBuf[:cn])
761
- if err != nil {
762
- return protocol.Header{}, nil, wrapErr(ErrChunk, "chunk header: "+err.Error())
763
- }
764
-
765
- if chk.MessageID != hdr.MessageID {
766
- return protocol.Header{}, nil, wrapErr(ErrChunk, "message_id mismatch")
767
- }
768
- if chk.ChunkIndex != ci {
769
- return protocol.Header{}, nil, wrapErr(ErrChunk, fmt.Sprintf(
770
- "chunk_index mismatch: expected %d, got %d", ci, chk.ChunkIndex))
771
- }
772
- if chk.ChunkCount != expectedChunkCount {
773
- return protocol.Header{}, nil, wrapErr(ErrChunk, "chunk_count mismatch")
774
- }
775
- if chk.TotalMessageLen != uint32(totalMsg) {
776
- return protocol.Header{}, nil, wrapErr(ErrChunk, "total_message_len mismatch")
777
- }
778
-
779
- chunkData := cn - protocol.HeaderSize
780
- if chunkData != int(chk.ChunkPayloadLen) {
781
- return protocol.Header{}, nil, wrapErr(ErrChunk, "chunk_payload_len mismatch")
782
- }
783
- if assembled+chunkData > int(hdr.PayloadLen) {
784
- return protocol.Header{}, nil, wrapErr(ErrChunk, "chunk exceeds payload_len")
785
- }
786
-
787
- copy(s.recvBuf[assembled:assembled+chunkData], pktBuf[protocol.HeaderSize:protocol.HeaderSize+chunkData])
788
- assembled += chunkData
789
- ci++
790
- }
791
-
792
- payload := s.recvBuf[:hdr.PayloadLen]
793
-
794
- // Validate batch directory
795
- if hdr.Flags&protocol.FlagBatch != 0 && hdr.ItemCount > 1 {
796
- dirBytes := int(hdr.ItemCount) * 8
797
- dirAligned := protocol.Align8(dirBytes)
798
- if len(payload) < dirAligned {
799
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "batch dir exceeds payload")
800
- }
801
- packedAreaLen := uint32(len(payload) - dirAligned)
802
- if err := protocol.BatchDirValidate(payload[:dirBytes], hdr.ItemCount, packedAreaLen); err != nil {
803
- return protocol.Header{}, nil, wrapErr(ErrProtocol, "batch dir: "+err.Error())
804
- }
805
- }
806
-
807
- return hdr, payload, nil
808
-}
809
-
323
func ensurePipeScratchBuf(buf *[]byte, needed int) []byte {
324
if len(*buf) < needed {
325
*buf = make([]byte, needed)
326
}
327
return (*buf)[:needed]
328
}
816
-
817
-// ---------------------------------------------------------------------------
818
-// Listener
819
-// ---------------------------------------------------------------------------
820
-
821
-// Listener is a listening Named Pipe endpoint.
822
-type Listener struct {
823
- mu sync.Mutex
824
- handle syscall.Handle
825
- config ServerConfig
826
- pipeName []uint16
827
- nextSessionID atomic.Uint64
828
- closing bool
829
- accepting bool
830
-}
831
-
832
-// Listen creates a listener on a Named Pipe derived from runDir + serviceName.
833
-func Listen(runDir, serviceName string, config ServerConfig) (*Listener, error) {
834
- pipeName, err := BuildPipeName(runDir, serviceName)
835
- if err != nil {
836
- return nil, err
837
- }
838
-
839
- bufSize := pipeBufferSize(config.PacketSize)
840
-
841
- // Create first instance with FILE_FLAG_FIRST_PIPE_INSTANCE
842
- handle, err := createPipeInstance(pipeName, bufSize, true)
843
- if err != nil {
844
- return nil, err
845
- }
846
-
847
- return &Listener{
848
- handle: handle,
849
- config: config,
850
- pipeName: pipeName,
851
- }, nil
852
-}
853
-
854
-// Handle returns the raw HANDLE.
855
-func (l *Listener) Handle() syscall.Handle {
856
- l.mu.Lock()
857
- defer l.mu.Unlock()
858
- return l.handle
859
-}
860
-
861
-// SetPayloadLimits updates the payload limits used for future handshakes.
862
-func (l *Listener) SetPayloadLimits(maxRequestPayloadBytes, maxResponsePayloadBytes uint32) {
863
- l.mu.Lock()
864
- defer l.mu.Unlock()
865
- l.config.MaxRequestPayloadBytes = maxRequestPayloadBytes
866
- l.config.MaxResponsePayloadBytes = maxResponsePayloadBytes
867
-}
868
-
869
-// Accept accepts one client connection. Performs the full handshake.
870
-func (l *Listener) Accept() (*Session, error) {
871
- sessionID := l.nextSessionID.Add(1)
872
- return l.AcceptWithConfig(sessionID, l.config)
873
-}
874
-
875
-// AcceptWithConfig accepts one client connection using a caller-provided
876
-// per-session server config and session ID.
877
-func (l *Listener) AcceptWithConfig(sessionID uint64, config ServerConfig) (*Session, error) {
878
- l.mu.Lock()
879
- if l.handle == syscall.InvalidHandle {
880
- l.mu.Unlock()
881
- return nil, wrapErr(ErrAccept, "listener closed")
882
- }
883
- sessionHandle := l.handle
884
- l.accepting = true
885
- l.mu.Unlock()
886
-
887
- err := connectNamedPipe(sessionHandle)
888
- if err != nil {
889
- // ERROR_PIPE_CONNECTED is fine — client connected between
890
- // CreateNamedPipe and ConnectNamedPipe
891
- if errno, ok := err.(syscall.Errno); !ok || errno != _ERROR_PIPE_CONNECTED {
892
- l.mu.Lock()
893
- l.accepting = false
894
- l.mu.Unlock()
895
- return nil, wrapErr(ErrAccept, err.Error())
896
- }
897
- }
898
-
899
- l.mu.Lock()
900
- l.accepting = false
901
- if l.closing {
902
- if l.handle == sessionHandle {
903
- l.handle = syscall.InvalidHandle
904
- }
905
- l.mu.Unlock()
906
- disconnectNamedPipe(sessionHandle)
907
- syscall.CloseHandle(sessionHandle)
908
- return nil, wrapErr(ErrAccept, "listener closed")
909
- }
910
-
911
- // Create new pipe instance for next client
912
- bufSize := pipeBufferSize(l.config.PacketSize)
913
- next, perr := createPipeInstance(l.pipeName, bufSize, false)
914
- if perr != nil {
915
- if l.handle == sessionHandle {
916
- l.handle = syscall.InvalidHandle
917
- }
918
- l.mu.Unlock()
919
- disconnectNamedPipe(sessionHandle)
920
- syscall.CloseHandle(sessionHandle)
921
- return nil, perr
922
- }
923
- l.handle = next
924
- l.mu.Unlock()
925
-
926
- // Handshake
927
- session, herr := serverHandshake(sessionHandle, &config, sessionID)
928
- if herr != nil {
929
- disconnectNamedPipe(sessionHandle)
930
- syscall.CloseHandle(sessionHandle)
931
- return nil, herr
932
- }
933
- return session, nil
934
-}
935
-
936
-// Close closes the listener.
937
-func (l *Listener) Close() {
938
- l.mu.Lock()
939
- handle := l.handle
940
- if handle == syscall.InvalidHandle {
941
- l.mu.Unlock()
942
- return
943
- }
944
- l.closing = true
945
- accepting := l.accepting
946
- if !accepting {
947
- l.handle = syscall.InvalidHandle
948
- }
949
- pipeName := l.pipeName
950
- l.mu.Unlock()
951
-
952
- if accepting && len(pipeName) > 0 && pipeName[0] != 0 {
953
- // A loopback connect reliably wakes a blocking ConnectNamedPipe()
954
- // so Accept() can observe shutdown and close the live listener handle
955
- // from the owning goroutine.
956
- wake, err := syscall.CreateFile(
957
- &pipeName[0],
958
- _GENERIC_READ|_GENERIC_WRITE,
959
- 0,
960
- nil,
961
- _OPEN_EXISTING,
962
- 0,
963
- 0,
964
- )
965
- if err == nil && wake != syscall.InvalidHandle && wake != 0 {
966
- syscall.CloseHandle(wake)
967
- }
968
- return
969
- }
970
-
971
- syscall.CloseHandle(handle)
972
-}
973
-
974
-// ---------------------------------------------------------------------------
975
-// Pipe instance creation
976
-// ---------------------------------------------------------------------------
977
-
978
-func createPipeInstance(pipeName []uint16, bufSize uint32, firstInstance bool) (syscall.Handle, error) {
979
- openMode := uint32(_PIPE_ACCESS_DUPLEX)
980
- if firstInstance {
981
- openMode |= _FILE_FLAG_FIRST_PIPE_INSTANCE
982
- }
983
-
984
- handle, err := createNamedPipe(
985
- &pipeName[0],
986
- openMode,
987
- _PIPE_TYPE_MESSAGE|_PIPE_READMODE_MESSAGE|_PIPE_WAIT,
988
- _PIPE_UNLIMITED_INSTANCES,
989
- bufSize,
990
- bufSize,
991
- 0,
992
- )
993
- if err != nil {
994
- errno, ok := err.(syscall.Errno)
995
- if ok && (errno == _ERROR_ACCESS_DENIED || errno == _ERROR_PIPE_BUSY) {
996
- return syscall.InvalidHandle, ErrAddrInUse
997
- }
998
- return syscall.InvalidHandle, wrapErr(ErrCreatePipe, err.Error())
999
- }
1000
- return handle, nil
1001
-}
1002
-
1003
-// ---------------------------------------------------------------------------
1004
-// Client handshake
1005
-// ---------------------------------------------------------------------------
1006
-
1007
-func clientHandshake(handle syscall.Handle, config *ClientConfig) (*Session, error) {
1008
- pktSize := applyDefault(config.PacketSize, defaultPacketSize)
1009
-
1010
- supported := config.SupportedProfiles
1011
- if supported == 0 {
1012
- supported = protocol.ProfileBaseline
1013
- }
1014
-
1015
- hello := protocol.Hello{
1016
- LayoutVersion: 1,
1017
- Flags: 0,
1018
- SupportedProfiles: supported,
1019
- PreferredProfiles: config.PreferredProfiles,
1020
- MaxRequestPayloadBytes: applyDefault(config.MaxRequestPayloadBytes, protocol.MaxPayloadDefault),
1021
- MaxRequestBatchItems: applyDefault(config.MaxRequestBatchItems, defaultBatchItems),
1022
- MaxResponsePayloadBytes: applyDefault(config.MaxResponsePayloadBytes, protocol.MaxPayloadDefault),
1023
- MaxResponseBatchItems: applyDefault(config.MaxResponseBatchItems, defaultBatchItems),
1024
- AuthToken: config.AuthToken,
1025
- PacketSize: pktSize,
1026
- }
1027
-
1028
- var helloBuf [helloPayloadSize]byte
1029
- hello.Encode(helloBuf[:])
1030
-
1031
- hdr := protocol.Header{
1032
- Magic: protocol.MagicMsg,
1033
- Version: protocol.Version,
1034
- HeaderLen: protocol.HeaderLen,
1035
- Kind: protocol.KindControl,
1036
- Flags: 0,
1037
- Code: protocol.CodeHello,
1038
- TransportStatus: protocol.StatusOK,
1039
- PayloadLen: helloPayloadSize,
1040
- ItemCount: 1,
1041
- MessageID: 0,
1042
- }
1043
-
1044
- var pkt [protocol.HeaderSize + helloPayloadSize]byte
1045
- hdr.Encode(pkt[:protocol.HeaderSize])
1046
- copy(pkt[protocol.HeaderSize:], helloBuf[:])
1047
-
1048
- // Send HELLO
1049
- if err := rawWrite(handle, pkt[:]); err != nil {
1050
- return nil, wrapErr(ErrSend, "hello send: "+err.Error())
1051
- }
1052
-
1053
- // Receive HELLO_ACK
1054
- var ackBuf [128]byte
1055
- an, err := rawRecv(handle, ackBuf[:])
1056
- if err != nil {
1057
- return nil, wrapErr(ErrRecv, "hello_ack recv: "+err.Error())
1058
- }
1059
-
1060
- ackHdr, err := protocol.DecodeHeader(ackBuf[:an])
1061
- if err != nil {
1062
- if errors.Is(err, protocol.ErrBadVersion) {
1063
- return nil, wrapErr(ErrIncompatible, "ack header version mismatch")
1064
- }
1065
- return nil, wrapErr(ErrProtocol, "ack header: "+err.Error())
1066
- }
1067
-
1068
- if ackHdr.Kind != protocol.KindControl || ackHdr.Code != protocol.CodeHelloAck {
1069
- return nil, wrapErr(ErrProtocol, "expected HELLO_ACK")
1070
- }
1071
-
1072
- if ackHdr.TransportStatus == protocol.StatusAuthFailed {
1073
- return nil, ErrAuthFailed
1074
- }
1075
- if ackHdr.TransportStatus == protocol.StatusUnsupported {
1076
- return nil, ErrNoProfile
1077
- }
1078
- if ackHdr.TransportStatus == protocol.StatusIncompatible {
1079
- return nil, ErrIncompatible
1080
- }
1081
- if ackHdr.TransportStatus == protocol.StatusLimitExceeded {
1082
- return nil, ErrLimitExceeded
1083
- }
1084
- if ackHdr.TransportStatus != protocol.StatusOK {
1085
- return nil, wrapErr(ErrHandshake, fmt.Sprintf("transport_status=%d", ackHdr.TransportStatus))
1086
- }
1087
-
1088
- if an < protocol.HeaderSize+helloAckPayloadSize {
1089
- return nil, wrapErr(ErrProtocol, "ack payload truncated")
1090
- }
1091
- ack, err := protocol.DecodeHelloAck(ackBuf[protocol.HeaderSize:an])
1092
- if err != nil {
1093
- if errors.Is(err, protocol.ErrBadLayout) &&
1094
- helloAckLayoutIncompatible(ackBuf[protocol.HeaderSize:an]) {
1095
- return nil, wrapErr(ErrIncompatible, "ack payload layout version mismatch")
1096
- }
1097
- return nil, wrapErr(ErrProtocol, "ack payload: "+err.Error())
1098
- }
1099
-
1100
- return &Session{
1101
- handle: handle,
1102
- role: RoleClient,
1103
- MaxRequestPayloadBytes: ack.AgreedMaxRequestPayloadBytes,
1104
- MaxRequestBatchItems: ack.AgreedMaxRequestBatchItems,
1105
- MaxResponsePayloadBytes: ack.AgreedMaxResponsePayloadBytes,
1106
- MaxResponseBatchItems: ack.AgreedMaxResponseBatchItems,
1107
- PacketSize: ack.AgreedPacketSize,
1108
- SelectedProfile: ack.SelectedProfile,
1109
- SessionID: ack.SessionID,
1110
- inflightIDs: make(map[uint64]struct{}),
1111
- }, nil
1112
-}
1113
-
1114
-// ---------------------------------------------------------------------------
1115
-// Server handshake
1116
-// ---------------------------------------------------------------------------
1117
-
1118
-func serverHandshake(handle syscall.Handle, config *ServerConfig, sessionID uint64) (*Session, error) {
1119
- serverPktSize := applyDefault(config.PacketSize, defaultPacketSize)
1120
- sRespPay := applyDefault(config.MaxResponsePayloadBytes, protocol.MaxPayloadDefault)
1121
- sProfiles := config.SupportedProfiles
1122
- if sProfiles == 0 {
1123
- sProfiles = protocol.ProfileBaseline
1124
- }
1125
- sPreferred := config.PreferredProfiles
1126
-
1127
- // Helper: send rejection
1128
- sendRejection := func(status uint16) {
1129
- ack := protocol.HelloAck{LayoutVersion: 1}
1130
- var ackPayBuf [helloAckPayloadSize]byte
1131
- ack.Encode(ackPayBuf[:])
1132
-
1133
- ackHdr := protocol.Header{
1134
- Magic: protocol.MagicMsg,
1135
- Version: protocol.Version,
1136
- HeaderLen: protocol.HeaderLen,
1137
- Kind: protocol.KindControl,
1138
- Code: protocol.CodeHelloAck,
1139
- TransportStatus: status,
1140
- PayloadLen: helloAckPayloadSize,
1141
- ItemCount: 1,
1142
- }
1143
-
1144
- var pkt [protocol.HeaderSize + helloAckPayloadSize]byte
1145
- ackHdr.Encode(pkt[:protocol.HeaderSize])
1146
- copy(pkt[protocol.HeaderSize:], ackPayBuf[:])
1147
- rawWrite(handle, pkt[:]) //nolint:errcheck
1148
- }
1149
-
1150
- // Receive HELLO
1151
- var buf [128]byte
1152
- n, err := rawRecv(handle, buf[:])
1153
- if err != nil {
1154
- return nil, wrapErr(ErrRecv, "hello recv: "+err.Error())
1155
- }
1156
-
1157
- hdr, err := protocol.DecodeHeader(buf[:n])
1158
- if err != nil {
1159
- if errors.Is(err, protocol.ErrBadVersion) &&
1160
- headerVersionIncompatible(buf[:n], protocol.CodeHello) {
1161
- sendRejection(protocol.StatusIncompatible)
1162
- return nil, ErrIncompatible
1163
- }
1164
- return nil, wrapErr(ErrProtocol, "hello header: "+err.Error())
1165
- }
1166
-
1167
- if hdr.Kind != protocol.KindControl || hdr.Code != protocol.CodeHello {
1168
- return nil, wrapErr(ErrProtocol, "expected HELLO")
1169
- }
1170
-
1171
- hello, err := protocol.DecodeHello(buf[protocol.HeaderSize:n])
1172
- if err != nil {
1173
- if errors.Is(err, protocol.ErrBadLayout) &&
1174
- helloLayoutIncompatible(buf[protocol.HeaderSize:n]) {
1175
- sendRejection(protocol.StatusIncompatible)
1176
- return nil, ErrIncompatible
1177
- }
1178
- return nil, wrapErr(ErrProtocol, "hello payload: "+err.Error())
1179
- }
1180
-
1181
- intersection := hello.SupportedProfiles & sProfiles
1182
-
1183
- if intersection == 0 {
1184
- sendRejection(protocol.StatusUnsupported)
1185
- return nil, ErrNoProfile
1186
- }
1187
-
1188
- if hello.AuthToken != config.AuthToken {
1189
- sendRejection(protocol.StatusAuthFailed)
1190
- return nil, ErrAuthFailed
1191
- }
1192
-
1193
- // Select profile
1194
- preferredIntersection := intersection & hello.PreferredProfiles & sPreferred
1195
- var selected uint32
1196
- if preferredIntersection != 0 {
1197
- selected = highestBit(preferredIntersection)
1198
- } else {
1199
- selected = highestBit(intersection)
1200
- }
1201
-
1202
- if hello.MaxRequestPayloadBytes > protocol.MaxPayloadCap {
1203
- sendRejection(protocol.StatusLimitExceeded)
1204
- return nil, ErrLimitExceeded
1205
- }
1206
-
1207
- // Negotiate limits
1208
- agreedReqPay := hello.MaxRequestPayloadBytes
1209
- agreedReqBat := hello.MaxRequestBatchItems
1210
- agreedRespPay := sRespPay
1211
- agreedRespBat := agreedReqBat
1212
- agreedPkt := minU32(hello.PacketSize, serverPktSize)
1213
- if agreedPkt <= protocol.HeaderSize {
1214
- sendRejection(protocol.StatusIncompatible)
1215
- return nil, ErrIncompatible
1216
- }
1217
-
1218
- // Send HELLO_ACK
1219
- ack := protocol.HelloAck{
1220
- LayoutVersion: 1,
1221
- Flags: 0,
1222
- ServerSupportedProfiles: sProfiles,
1223
- IntersectionProfiles: intersection,
1224
- SelectedProfile: selected,
1225
- AgreedMaxRequestPayloadBytes: agreedReqPay,
1226
- AgreedMaxRequestBatchItems: agreedReqBat,
1227
- AgreedMaxResponsePayloadBytes: agreedRespPay,
1228
- AgreedMaxResponseBatchItems: agreedRespBat,
1229
- AgreedPacketSize: agreedPkt,
1230
- SessionID: sessionID,
1231
- }
1232
-
1233
- var ackPayBuf [helloAckPayloadSize]byte
1234
- ack.Encode(ackPayBuf[:])
1235
-
1236
- ackHdr := protocol.Header{
1237
- Magic: protocol.MagicMsg,
1238
- Version: protocol.Version,
1239
- HeaderLen: protocol.HeaderLen,
1240
- Kind: protocol.KindControl,
1241
- Code: protocol.CodeHelloAck,
1242
- TransportStatus: protocol.StatusOK,
1243
- PayloadLen: helloAckPayloadSize,
1244
- ItemCount: 1,
1245
- }
1246
-
1247
- var pkt [protocol.HeaderSize + helloAckPayloadSize]byte
1248
- ackHdr.Encode(pkt[:protocol.HeaderSize])
1249
- copy(pkt[protocol.HeaderSize:], ackPayBuf[:])
1250
-
1251
- if err := rawWrite(handle, pkt[:]); err != nil {
1252
- return nil, wrapErr(ErrSend, "hello_ack send: "+err.Error())
1253
- }
1254
-
1255
- return &Session{
1256
- handle: handle,
1257
- role: RoleServer,
1258
- MaxRequestPayloadBytes: agreedReqPay,
1259
- MaxRequestBatchItems: agreedReqBat,
1260
- MaxResponsePayloadBytes: agreedRespPay,
1261
- MaxResponseBatchItems: agreedRespBat,
1262
- PacketSize: agreedPkt,
1263
- SelectedProfile: selected,
1264
- SessionID: sessionID,
1265
- inflightIDs: make(map[uint64]struct{}),
1266
- }, nil
1267
-}
src/go/pkg/netipc/transport/windows/pipe_handshake.go
new
+128
@@ -0,0 +1,128 @@
1
+//go:build windows
2
+
3
+package windows
4
+
5
+import (
6
+ "fmt"
7
+ "syscall"
8
+
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
10
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/internal/framing"
11
+)
12
+
13
+func headerVersionIncompatible(buf []byte, expectedCode uint16) bool {
14
+ return framing.HeaderVersionIncompatible(buf, expectedCode)
15
+}
16
+
17
+func helloLayoutIncompatible(buf []byte) bool {
18
+ return framing.HelloLayoutIncompatible(buf)
19
+}
20
+
21
+func helloAckLayoutIncompatible(buf []byte) bool {
22
+ return framing.HelloAckLayoutIncompatible(buf)
23
+}
24
+
25
+func clientHandshake(handle syscall.Handle, config *ClientConfig) (*Session, error) {
26
+ pktSize := applyDefault(config.PacketSize, defaultPacketSize)
27
+
28
+ ack, err := framing.ClientHandshake(framing.ClientHandshakeConfig{
29
+ Hello: framing.HelloConfig{
30
+ SupportedProfiles: config.SupportedProfiles,
31
+ PreferredProfiles: config.PreferredProfiles,
32
+ MaxRequestPayloadBytes: applyDefault(config.MaxRequestPayloadBytes, protocol.MaxPayloadDefault),
33
+ MaxRequestBatchItems: applyDefault(config.MaxRequestBatchItems, defaultBatchItems),
34
+ MaxResponsePayloadBytes: applyDefault(config.MaxResponsePayloadBytes, protocol.MaxPayloadDefault),
35
+ MaxResponseBatchItems: applyDefault(config.MaxResponseBatchItems, defaultBatchItems),
36
+ AuthToken: config.AuthToken,
37
+ PacketSize: pktSize,
38
+ },
39
+ Send: func(pkt []byte) error { return rawWrite(handle, pkt) },
40
+ Recv: func(dst []byte) (int, error) { return rawRecv(handle, dst) },
41
+ StatusError: helloAckStatusError,
42
+ ErrSend: func(msg string) error { return wrapErr(ErrSend, msg) },
43
+ ErrRecv: func(msg string) error { return wrapErr(ErrRecv, msg) },
44
+ ErrProtocol: func(msg string) error { return wrapErr(ErrProtocol, msg) },
45
+ ErrIncompatible: func(msg string) error { return wrapErr(ErrIncompatible, msg) },
46
+ })
47
+ if err != nil {
48
+ return nil, err
49
+ }
50
+
51
+ return &Session{
52
+ handle: handle,
53
+ role: RoleClient,
54
+ MaxRequestPayloadBytes: ack.AgreedMaxRequestPayloadBytes,
55
+ MaxRequestBatchItems: ack.AgreedMaxRequestBatchItems,
56
+ MaxResponsePayloadBytes: ack.AgreedMaxResponsePayloadBytes,
57
+ MaxResponseBatchItems: ack.AgreedMaxResponseBatchItems,
58
+ PacketSize: ack.AgreedPacketSize,
59
+ SelectedProfile: ack.SelectedProfile,
60
+ SessionID: ack.SessionID,
61
+ inflightIDs: make(map[uint64]struct{}),
62
+ }, nil
63
+}
64
+
65
+func helloAckStatusError(status uint16) error {
66
+ switch status {
67
+ case protocol.StatusOK:
68
+ return nil
69
+ case protocol.StatusAuthFailed:
70
+ return ErrAuthFailed
71
+ case protocol.StatusUnsupported:
72
+ return ErrNoProfile
73
+ case protocol.StatusIncompatible:
74
+ return ErrIncompatible
75
+ case protocol.StatusLimitExceeded:
76
+ return ErrLimitExceeded
77
+ default:
78
+ return wrapErr(ErrHandshake, fmt.Sprintf("transport_status=%d", status))
79
+ }
80
+}
81
+
82
+func serverHandshake(handle syscall.Handle, config *ServerConfig, sessionID uint64) (*Session, error) {
83
+ serverPktSize := applyDefault(config.PacketSize, defaultPacketSize)
84
+ sRespPay := applyDefault(config.MaxResponsePayloadBytes, protocol.MaxPayloadDefault)
85
+
86
+ ack, err := framing.ServerHandshake(framing.ServerHandshakeConfig{
87
+ ServerHelloConfig: framing.ServerHelloConfig{
88
+ PacketSize: serverPktSize,
89
+ MaxResponsePayloadBytes: sRespPay,
90
+ SupportedProfiles: config.SupportedProfiles,
91
+ PreferredProfiles: config.PreferredProfiles,
92
+ AuthToken: config.AuthToken,
93
+ },
94
+ SessionID: sessionID,
95
+ Recv: func(dst []byte) (int, error) { return rawRecv(handle, dst) },
96
+ SendAck: func(status uint16, ack protocol.HelloAck) error { return sendHelloAck(handle, status, ack) },
97
+ StatusError: helloAckStatusError,
98
+ ErrRecv: func(msg string) error { return wrapErr(ErrRecv, msg) },
99
+ ErrSend: func(msg string) error { return wrapErr(ErrSend, msg) },
100
+ ErrProtocol: func(msg string) error { return wrapErr(ErrProtocol, msg) },
101
+ ErrIncompatible: func(msg string) error { return wrapErr(ErrIncompatible, msg) },
102
+ })
103
+ if err != nil {
104
+ return nil, err
105
+ }
106
+
107
+ return &Session{
108
+ handle: handle,
109
+ role: RoleServer,
110
+ MaxRequestPayloadBytes: ack.AgreedMaxRequestPayloadBytes,
111
+ MaxRequestBatchItems: ack.AgreedMaxRequestBatchItems,
112
+ MaxResponsePayloadBytes: ack.AgreedMaxResponsePayloadBytes,
113
+ MaxResponseBatchItems: ack.AgreedMaxResponseBatchItems,
114
+ PacketSize: ack.AgreedPacketSize,
115
+ SelectedProfile: ack.SelectedProfile,
116
+ SessionID: sessionID,
117
+ inflightIDs: make(map[uint64]struct{}),
118
+ }, nil
119
+}
120
+
121
+func sendRejection(handle syscall.Handle, status uint16) {
122
+ _ = sendHelloAck(handle, status, protocol.HelloAck{LayoutVersion: 1})
123
+}
124
+
125
+func sendHelloAck(handle syscall.Handle, status uint16, ack protocol.HelloAck) error {
126
+ pkt := framing.BuildHelloAckPacket(status, ack)
127
+ return rawWrite(handle, pkt[:])
128
+}
src/go/pkg/netipc/transport/windows/pipe_integration_test.go
+53
-2
@@ -139,6 +139,57 @@ func TestPipeSingleClientPingPong(t *testing.T) {
139
}
140
}
141
142
+func TestPipeWaitReadable(t *testing.T) {
143
+ client, server := sessionPair(t, defaultServerConfig(), defaultClientConfig())
144
+
145
+ ready, err := server.WaitReadable(1)
146
+ if err != nil {
147
+ t.Fatalf("WaitReadable before send returned error: %v", err)
148
+ }
149
+ if ready {
150
+ t.Fatal("WaitReadable before send returned ready")
151
+ }
152
+
153
+ payload := []byte("wake")
154
+ hdr := protocol.Header{
155
+ Kind: protocol.KindRequest,
156
+ Code: protocol.MethodIncrement,
157
+ ItemCount: 1,
158
+ MessageID: 77,
159
+ }
160
+ if err := client.Send(&hdr, payload); err != nil {
161
+ t.Fatalf("client Send: %v", err)
162
+ }
163
+
164
+ ready, err = server.WaitReadable(1000)
165
+ if err != nil {
166
+ t.Fatalf("WaitReadable after send returned error: %v", err)
167
+ }
168
+ if !ready {
169
+ t.Fatal("WaitReadable after send returned not ready")
170
+ }
171
+
172
+ rHdr, rPayload, err := server.Receive(make([]byte, 4096))
173
+ if err != nil {
174
+ t.Fatalf("server Receive: %v", err)
175
+ }
176
+ if rHdr.MessageID != hdr.MessageID || !bytes.Equal(rPayload, payload) {
177
+ t.Fatalf("unexpected WaitReadable receive: hdr=%+v payload=%q", rHdr, rPayload)
178
+ }
179
+}
180
+
181
+func TestPipeListenerSetPayloadLimits(t *testing.T) {
182
+ service := uniquePipeService(t)
183
+ listener := startListener(t, testPipeRunDir, service, defaultServerConfig())
184
+ defer listener.Close()
185
+
186
+ listener.SetPayloadLimits(1234, 5678)
187
+ if listener.config.MaxRequestPayloadBytes != 1234 ||
188
+ listener.config.MaxResponsePayloadBytes != 5678 {
189
+ t.Fatalf("listener payload limits not updated: %+v", listener.config)
190
+ }
191
+}
192
+
193
func TestPipeMultiClient(t *testing.T) {
194
service := uniquePipeService(t)
195
listener := startListener(t, testPipeRunDir, service, defaultServerConfig())
@@ -1022,8 +1073,8 @@ func TestPipeHandleAndRole(t *testing.T) {
1073
if client.Handle() == syscall.InvalidHandle || server.Handle() == syscall.InvalidHandle {
1074
t.Fatal("session handles should be valid")
1075
}
1025
- if client.GetRole() != RoleClient || server.GetRole() != RoleServer {
1026
- t.Fatalf("unexpected roles client=%d server=%d", client.GetRole(), server.GetRole())
1076
+ if client.Role() != RoleClient || server.Role() != RoleServer {
1077
+ t.Fatalf("unexpected roles client=%d server=%d", client.Role(), server.Role())
1078
}
1079
}
1080
src/go/pkg/netipc/transport/windows/pipe_listener.go
new
+186
@@ -0,0 +1,186 @@
1
+//go:build windows
2
+
3
+package windows
4
+
5
+import (
6
+ "sync"
7
+ "sync/atomic"
8
+ "syscall"
9
+)
10
+
11
+// Listener is a listening Named Pipe endpoint.
12
+type Listener struct {
13
+ mu sync.Mutex
14
+ handle syscall.Handle
15
+ config ServerConfig
16
+ pipeName []uint16
17
+ nextSessionID atomic.Uint64
18
+ closing bool
19
+ accepting bool
20
+}
21
+
22
+// Listen creates a listener on a Named Pipe derived from runDir + serviceName.
23
+func Listen(runDir, serviceName string, config ServerConfig) (*Listener, error) {
24
+ pipeName, err := BuildPipeName(runDir, serviceName)
25
+ if err != nil {
26
+ return nil, err
27
+ }
28
+
29
+ bufSize := pipeBufferSize(config.PacketSize)
30
+ handle, err := createPipeInstance(pipeName, bufSize, true)
31
+ if err != nil {
32
+ return nil, err
33
+ }
34
+
35
+ return &Listener{
36
+ handle: handle,
37
+ config: config,
38
+ pipeName: pipeName,
39
+ }, nil
40
+}
41
+
42
+// Handle returns the raw HANDLE.
43
+func (l *Listener) Handle() syscall.Handle {
44
+ l.mu.Lock()
45
+ defer l.mu.Unlock()
46
+ return l.handle
47
+}
48
+
49
+// SetPayloadLimits updates the payload limits used for future handshakes.
50
+func (l *Listener) SetPayloadLimits(maxRequestPayloadBytes, maxResponsePayloadBytes uint32) {
51
+ l.mu.Lock()
52
+ defer l.mu.Unlock()
53
+ l.config.MaxRequestPayloadBytes = maxRequestPayloadBytes
54
+ l.config.MaxResponsePayloadBytes = maxResponsePayloadBytes
55
+}
56
+
57
+// Accept accepts one client connection. Performs the full handshake.
58
+func (l *Listener) Accept() (*Session, error) {
59
+ sessionID := l.nextSessionID.Add(1)
60
+ l.mu.Lock()
61
+ config := l.config
62
+ l.mu.Unlock()
63
+ return l.AcceptWithConfig(sessionID, config)
64
+}
65
+
66
+// AcceptWithConfig accepts one client connection using a caller-provided
67
+// per-session server config and session ID.
68
+func (l *Listener) AcceptWithConfig(sessionID uint64, config ServerConfig) (*Session, error) {
69
+ l.mu.Lock()
70
+ if l.handle == syscall.InvalidHandle {
71
+ l.mu.Unlock()
72
+ return nil, wrapErr(ErrAccept, "listener closed")
73
+ }
74
+ sessionHandle := l.handle
75
+ l.accepting = true
76
+ l.mu.Unlock()
77
+
78
+ err := connectNamedPipe(sessionHandle)
79
+ if err != nil {
80
+ if errno, ok := err.(syscall.Errno); !ok || errno != _ERROR_PIPE_CONNECTED {
81
+ l.mu.Lock()
82
+ l.accepting = false
83
+ l.mu.Unlock()
84
+ return nil, wrapErr(ErrAccept, err.Error())
85
+ }
86
+ }
87
+
88
+ l.mu.Lock()
89
+ l.accepting = false
90
+ if l.closing {
91
+ if l.handle == sessionHandle {
92
+ l.handle = syscall.InvalidHandle
93
+ }
94
+ l.mu.Unlock()
95
+ disconnectNamedPipe(sessionHandle)
96
+ syscall.CloseHandle(sessionHandle)
97
+ return nil, wrapErr(ErrAccept, "listener closed")
98
+ }
99
+
100
+ bufSize := pipeBufferSize(l.config.PacketSize)
101
+ next, perr := createPipeInstance(l.pipeName, bufSize, false)
102
+ if perr != nil {
103
+ if l.handle == sessionHandle {
104
+ l.handle = syscall.InvalidHandle
105
+ }
106
+ l.mu.Unlock()
107
+ disconnectNamedPipe(sessionHandle)
108
+ syscall.CloseHandle(sessionHandle)
109
+ return nil, perr
110
+ }
111
+ l.handle = next
112
+ l.mu.Unlock()
113
+
114
+ session, herr := serverHandshake(sessionHandle, &config, sessionID)
115
+ if herr != nil {
116
+ disconnectNamedPipe(sessionHandle)
117
+ syscall.CloseHandle(sessionHandle)
118
+ return nil, herr
119
+ }
120
+ return session, nil
121
+}
122
+
123
+// Close closes the listener.
124
+func (l *Listener) Close() {
125
+ l.mu.Lock()
126
+ handle := l.handle
127
+ if handle == syscall.InvalidHandle {
128
+ l.mu.Unlock()
129
+ return
130
+ }
131
+ l.closing = true
132
+ accepting := l.accepting
133
+ if !accepting {
134
+ l.handle = syscall.InvalidHandle
135
+ }
136
+ pipeName := l.pipeName
137
+ l.mu.Unlock()
138
+
139
+ if accepting && len(pipeName) > 0 && pipeName[0] != 0 {
140
+ wake, err := syscall.CreateFile(
141
+ &pipeName[0],
142
+ _GENERIC_READ|_GENERIC_WRITE,
143
+ 0,
144
+ nil,
145
+ _OPEN_EXISTING,
146
+ 0,
147
+ 0,
148
+ )
149
+ if err == nil && wake != syscall.InvalidHandle && wake != 0 {
150
+ syscall.CloseHandle(wake)
151
+ return
152
+ }
153
+ l.mu.Lock()
154
+ if l.handle == handle {
155
+ l.handle = syscall.InvalidHandle
156
+ }
157
+ l.mu.Unlock()
158
+ }
159
+
160
+ syscall.CloseHandle(handle)
161
+}
162
+
163
+func createPipeInstance(pipeName []uint16, bufSize uint32, firstInstance bool) (syscall.Handle, error) {
164
+ openMode := uint32(_PIPE_ACCESS_DUPLEX)
165
+ if firstInstance {
166
+ openMode |= _FILE_FLAG_FIRST_PIPE_INSTANCE
167
+ }
168
+
169
+ handle, err := createNamedPipe(
170
+ &pipeName[0],
171
+ openMode,
172
+ _PIPE_TYPE_MESSAGE|_PIPE_READMODE_MESSAGE|_PIPE_WAIT,
173
+ _PIPE_UNLIMITED_INSTANCES,
174
+ bufSize,
175
+ bufSize,
176
+ 0,
177
+ )
178
+ if err != nil {
179
+ errno, ok := err.(syscall.Errno)
180
+ if ok && (errno == _ERROR_ACCESS_DENIED || errno == _ERROR_PIPE_BUSY) {
181
+ return syscall.InvalidHandle, ErrAddrInUse
182
+ }
183
+ return syscall.InvalidHandle, wrapErr(ErrCreatePipe, err.Error())
184
+ }
185
+ return handle, nil
186
+}
src/go/pkg/netipc/transport/windows/pipe_receive.go
new
+39
@@ -0,0 +1,39 @@
1
+//go:build windows
2
+
3
+package windows
4
+
5
+import (
6
+ "errors"
7
+ "syscall"
8
+
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
10
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/internal/framing"
11
+)
12
+
13
+// Receive reads one logical message. buf is a scratch buffer.
14
+func (s *Session) Receive(buf []byte) (protocol.Header, []byte, error) {
15
+ if s.handle == syscall.InvalidHandle {
16
+ return protocol.Header{}, nil, wrapErr(ErrBadParam, "session closed")
17
+ }
18
+
19
+ return framing.SessionReceive(framing.SessionReceiveConfig{
20
+ RoleServer: s.role == RoleServer,
21
+ PacketSize: s.PacketSize,
22
+ MaxRequestPayloadBytes: s.MaxRequestPayloadBytes,
23
+ MaxRequestBatchItems: s.MaxRequestBatchItems,
24
+ MaxResponsePayloadBytes: s.MaxResponsePayloadBytes,
25
+ MaxResponseBatchItems: s.MaxResponseBatchItems,
26
+ InflightIDs: s.inflightIDs,
27
+ RecvBuf: &s.recvBuf,
28
+ PacketBuf: &s.pktBuf,
29
+ Recv: func(dst []byte) (int, error) { return rawRecv(s.handle, dst) },
30
+ EnsurePacketScratch: ensurePipeScratchBuf,
31
+ IsRecvDisconnect: func(err error) bool { return errors.Is(err, ErrDisconnected) },
32
+ FailAllInflight: s.failAllInflight,
33
+ ErrLimitExceeded: func(msg string) error { return wrapErr(ErrLimitExceeded, msg) },
34
+ ErrProtocol: func(msg string) error { return wrapErr(ErrProtocol, msg) },
35
+ ErrChunk: func(msg string) error { return wrapErr(ErrChunk, msg) },
36
+ ErrUnknownMsgID: func(msg string) error { return wrapErr(ErrUnknownMsgID, msg) },
37
+ ErrRecv: func(msg string) error { return wrapErr(ErrRecv, msg) },
38
+ }, buf)
39
+}
src/go/pkg/netipc/transport/windows/pipe_send.go
new
+42
@@ -0,0 +1,42 @@
1
+//go:build windows
2
+
3
+package windows
4
+
5
+import (
6
+ "errors"
7
+ "syscall"
8
+
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
10
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/internal/framing"
11
+)
12
+
13
+// Send sends one logical message. Fills magic/version/header_len/payload_len.
14
+func (s *Session) Send(hdr *protocol.Header, payload []byte) error {
15
+ if s.handle == syscall.InvalidHandle {
16
+ return wrapErr(ErrBadParam, "session closed")
17
+ }
18
+
19
+ return framing.SessionSend(framing.SessionSendConfig{
20
+ RoleClient: s.role == RoleClient,
21
+ PacketSize: s.PacketSize,
22
+ InflightIDs: &s.inflightIDs,
23
+ FailAllInflight: s.failAllInflight,
24
+ IsSendDisconnect: func(err error) bool { return errors.Is(err, ErrDisconnected) },
25
+ SendFirstPacket: func(packetHdr *protocol.Header, packetPayload []byte, packetLen int) error {
26
+ msg := ensurePipeScratchBuf(&s.sendBuf, packetLen)
27
+ packetHdr.Encode(msg[:protocol.HeaderSize])
28
+ copy(msg[protocol.HeaderSize:], packetPayload)
29
+ return rawSendMsg(s.handle, msg[:packetLen])
30
+ },
31
+ SendChunk: func(chk protocol.ChunkHeader, chunkPayload []byte) error {
32
+ pktLen := protocol.HeaderSize + len(chunkPayload)
33
+ msg := ensurePipeScratchBuf(&s.sendBuf, pktLen)
34
+ chk.Encode(msg[:protocol.HeaderSize])
35
+ copy(msg[protocol.HeaderSize:], chunkPayload)
36
+ return rawSendMsg(s.handle, msg[:pktLen])
37
+ },
38
+ ErrLimitExceeded: func(msg string) error { return wrapErr(ErrLimitExceeded, msg) },
39
+ ErrDuplicateMsgID: func(msg string) error { return wrapErr(ErrDuplicateMsgID, msg) },
40
+ ErrBadParam: func(msg string) error { return wrapErr(ErrBadParam, msg) },
41
+ }, hdr, payload)
42
+}
src/go/pkg/netipc/transport/windows/pipe_send_test.go
new
+32
@@ -0,0 +1,32 @@
1
+//go:build windows
2
+
3
+package windows
4
+
5
+import (
6
+ "testing"
7
+
8
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/protocol"
9
+ "github.com/netdata/netdata/go/plugins/pkg/netipc/transport/internal/framing"
10
+)
11
+
12
+func TestHeaderPayloadLenBounds(t *testing.T) {
13
+ if _, _, ok := framing.HeaderPayloadLen(-1); ok {
14
+ t.Fatal("negative payload length should fail")
15
+ }
16
+
17
+ maxInt := int(^uint(0) >> 1)
18
+ if _, _, ok := framing.HeaderPayloadLen(maxInt); ok {
19
+ t.Fatal("payload length that overflows int total length should fail")
20
+ }
21
+
22
+ protocolLimit := uint64(^uint32(0)) - uint64(protocol.HeaderSize)
23
+ if protocolLimit <= uint64(maxInt) {
24
+ got, _, ok := framing.HeaderPayloadLen(int(protocolLimit))
25
+ if !ok {
26
+ t.Fatal("protocol-limit payload should pass")
27
+ }
28
+ if uint64(got) != uint64(^uint32(0)) {
29
+ t.Fatalf("total length = %d, want %d", got, uint64(^uint32(0)))
30
+ }
31
+ }
32
+}
src/go/pkg/netipc/transport/windows/pipe_session.go
new
+184
@@ -0,0 +1,184 @@
1
+//go:build windows
2
+
3
+package windows
4
+
5
+import (
6
+ "syscall"
7
+ "time"
8
+)
9
+
10
+// Role distinguishes client vs server sessions.
11
+type Role int
12
+
13
+const (
14
+ RoleClient Role = 1
15
+ RoleServer Role = 2
16
+)
17
+
18
+// spinWaitIterations limits cooperative polling before falling back to sleep.
19
+const spinWaitIterations = 256
20
+
21
+// ClientConfig configures a client connection.
22
+type ClientConfig struct {
23
+ SupportedProfiles uint32
24
+ PreferredProfiles uint32
25
+ MaxRequestPayloadBytes uint32 // 0 = use default (1024)
26
+ MaxRequestBatchItems uint32 // 0 = use default (1)
27
+ MaxResponsePayloadBytes uint32
28
+ MaxResponseBatchItems uint32
29
+ AuthToken uint64
30
+ PacketSize uint32 // 0 = use default (65536)
31
+}
32
+
33
+// ServerConfig configures a listener and its accepted sessions.
34
+type ServerConfig struct {
35
+ SupportedProfiles uint32
36
+ PreferredProfiles uint32
37
+ MaxRequestPayloadBytes uint32
38
+ MaxRequestBatchItems uint32
39
+ MaxResponsePayloadBytes uint32
40
+ MaxResponseBatchItems uint32
41
+ AuthToken uint64
42
+ PacketSize uint32 // 0 = use default (65536)
43
+}
44
+
45
+// Session is a connected Named Pipe session (client or server side).
46
+type Session struct {
47
+ handle syscall.Handle
48
+ role Role
49
+
50
+ MaxRequestPayloadBytes uint32
51
+ MaxRequestBatchItems uint32
52
+ MaxResponsePayloadBytes uint32
53
+ MaxResponseBatchItems uint32
54
+ PacketSize uint32
55
+ SelectedProfile uint32
56
+ SessionID uint64
57
+
58
+ recvBuf []byte
59
+ sendBuf []byte
60
+ pktBuf []byte
61
+
62
+ inflightIDs map[uint64]struct{}
63
+}
64
+
65
+func (s *Session) failAllInflight() {
66
+ if s.role != RoleClient || len(s.inflightIDs) == 0 {
67
+ return
68
+ }
69
+ clear(s.inflightIDs)
70
+}
71
+
72
+// Handle returns the raw HANDLE for WaitForSingleObject integration.
73
+func (s *Session) Handle() syscall.Handle {
74
+ return s.handle
75
+}
76
+
77
+// Role returns the session role.
78
+func (s *Session) Role() Role {
79
+ return s.role
80
+}
81
+
82
+// GetRole returns the session role.
83
+// Deprecated: use Role.
84
+func (s *Session) GetRole() Role {
85
+ return s.Role()
86
+}
87
+
88
+// WaitReadable waits until bytes are available to read or the timeout expires.
89
+func (s *Session) WaitReadable(timeoutMs uint32) (bool, error) {
90
+ if s.handle == syscall.InvalidHandle {
91
+ return false, wrapErr(ErrBadParam, "session closed")
92
+ }
93
+
94
+ deadline := time.Now().Add(time.Duration(timeoutMs) * time.Millisecond)
95
+ yielded := false
96
+ for {
97
+ available, err := peekNamedPipeAvailable(s.handle)
98
+ if err != nil {
99
+ if isDisconnectError(err) {
100
+ s.failAllInflight()
101
+ return false, ErrDisconnected
102
+ }
103
+ return false, wrapErr(ErrRecv, err.Error())
104
+ }
105
+ if available > 0 {
106
+ return true, nil
107
+ }
108
+ if !time.Now().Before(deadline) {
109
+ return false, nil
110
+ }
111
+ if !yielded {
112
+ yielded = true
113
+ for i := 0; i < spinWaitIterations; i++ {
114
+ procSwitchToThread.Call()
115
+ available, err = peekNamedPipeAvailable(s.handle)
116
+ if err != nil {
117
+ if isDisconnectError(err) {
118
+ s.failAllInflight()
119
+ return false, ErrDisconnected
120
+ }
121
+ return false, wrapErr(ErrRecv, err.Error())
122
+ }
123
+ if available > 0 {
124
+ return true, nil
125
+ }
126
+ if !time.Now().Before(deadline) {
127
+ return false, nil
128
+ }
129
+ }
130
+ continue
131
+ }
132
+ time.Sleep(time.Millisecond)
133
+ }
134
+}
135
+
136
+// Close closes the session and releases resources.
137
+func (s *Session) Close() {
138
+ if s.handle != syscall.InvalidHandle {
139
+ if s.role == RoleServer {
140
+ flushFileBuffers(s.handle)
141
+ disconnectNamedPipe(s.handle)
142
+ }
143
+ syscall.CloseHandle(s.handle)
144
+ s.handle = syscall.InvalidHandle
145
+ }
146
+ s.recvBuf = nil
147
+ s.sendBuf = nil
148
+ s.pktBuf = nil
149
+ s.failAllInflight()
150
+}
151
+
152
+// Connect establishes a session to a server pipe derived from runDir + serviceName.
153
+func Connect(runDir, serviceName string, config *ClientConfig) (*Session, error) {
154
+ pipeName, err := BuildPipeName(runDir, serviceName)
155
+ if err != nil {
156
+ return nil, err
157
+ }
158
+
159
+ handle, err := syscall.CreateFile(
160
+ &pipeName[0],
161
+ _GENERIC_READ|_GENERIC_WRITE,
162
+ 0,
163
+ nil,
164
+ _OPEN_EXISTING,
165
+ 0,
166
+ 0,
167
+ )
168
+ if err != nil {
169
+ return nil, wrapErr(ErrConnect, err.Error())
170
+ }
171
+
172
+ mode := uint32(_PIPE_READMODE_MESSAGE)
173
+ if err := setNamedPipeHandleState(handle, &mode); err != nil {
174
+ syscall.CloseHandle(handle)
175
+ return nil, wrapErr(ErrConnect, "SetNamedPipeHandleState: "+err.Error())
176
+ }
177
+
178
+ session, herr := clientHandshake(handle, config)
179
+ if herr != nil {
180
+ syscall.CloseHandle(handle)
181
+ return nil, herr
182
+ }
183
+ return session, nil
184
+}
src/go/pkg/netipc/transport/windows/pipe_test.go
+26
@@ -30,6 +30,18 @@ func TestMinU32(t *testing.T) {
30
}
31
}
32
33
+func TestMaxU32(t *testing.T) {
34
+ if got := maxU32(9, 1); got != 9 {
35
+ t.Fatalf("maxU32(9, 1) = %d, want 9", got)
36
+ }
37
+ if got := maxU32(1, 9); got != 9 {
38
+ t.Fatalf("maxU32(1, 9) = %d, want 9", got)
39
+ }
40
+ if got := maxU32(5, 5); got != 5 {
41
+ t.Fatalf("maxU32(5, 5) = %d, want 5", got)
42
+ }
43
+}
44
+
45
func TestHighestBit(t *testing.T) {
46
cases := []struct {
47
mask uint32
@@ -218,3 +230,17 @@ func TestSetNamedPipeHandleStateInvalidHandle(t *testing.T) {
230
t.Fatal("setNamedPipeHandleState on invalid handle should fail")
231
}
232
}
233
+
234
+func TestWaitReadableClosedSession(t *testing.T) {
235
+ session := &Session{handle: syscall.InvalidHandle}
236
+ ready, err := session.WaitReadable(1)
237
+ if err == nil {
238
+ t.Fatal("WaitReadable on closed session should fail")
239
+ }
240
+ if ready {
241
+ t.Fatal("WaitReadable on closed session returned ready")
242
+ }
243
+ if !errors.Is(err, ErrBadParam) {
244
+ t.Fatalf("WaitReadable on closed session = %v, want ErrBadParam", err)
245
+ }
246
+}
src/go/pkg/netipc/transport/windows/shm.go
+5
-1
@@ -160,7 +160,11 @@ type WinShmContext struct {
160
}
161
162
// Role returns the context role.
163
-func (c *WinShmContext) GetRole() WinShmRole { return c.role }
163
+func (c *WinShmContext) Role() WinShmRole { return c.role }
164
+
165
+// GetRole returns the context role.
166
+// Deprecated: use Role.
167
+func (c *WinShmContext) GetRole() WinShmRole { return c.Role() }
168
169
// ---------------------------------------------------------------------------
170
// Server API
src/go/pkg/netipc/transport/windows/shm_test.go
+4
-4
@@ -111,11 +111,11 @@ func TestWinShmCreateAttachAndCloseValidation(t *testing.T) {
111
}
112
defer client.WinShmClose()
113
114
- if server.GetRole() != WinShmRoleServer {
115
- t.Fatalf("server role = %d, want %d", server.GetRole(), WinShmRoleServer)
114
+ if server.Role() != WinShmRoleServer {
115
+ t.Fatalf("server role = %d, want %d", server.Role(), WinShmRoleServer)
116
}
117
- if client.GetRole() != WinShmRoleClient {
118
- t.Fatalf("client role = %d, want %d", client.GetRole(), WinShmRoleClient)
117
+ if client.Role() != WinShmRoleClient {
118
+ t.Fatalf("client role = %d, want %d", client.Role(), WinShmRoleClient)
119
}
120
}
121
src/libnetdata/netipc/include/netipc/netipc_protocol.h
+257
@@ -56,6 +56,8 @@ extern "C" {
56
#define NIPC_METHOD_INCREMENT 1u
57
#define NIPC_METHOD_CGROUPS_SNAPSHOT 2u
58
#define NIPC_METHOD_STRING_REVERSE 3u
59
+#define NIPC_METHOD_CGROUPS_LOOKUP 4u
60
+#define NIPC_METHOD_APPS_LOOKUP 5u
61
62
/* Profile bits */
63
#define NIPC_PROFILE_BASELINE 0x01u
@@ -73,6 +75,9 @@ extern "C" {
75
/* Alignment for batch items and cgroups items */
76
#define NIPC_ALIGNMENT 8u
77
78
+/* Common lookup sentinel values */
79
+#define NIPC_UID_UNSET 0xFFFFFFFFu
80
+
81
/* ------------------------------------------------------------------ */
82
/* Error codes */
83
/* ------------------------------------------------------------------ */
@@ -94,6 +99,39 @@ typedef enum {
99
NIPC_ERR_NOT_READY, /* client not connected / service unavailable */
100
} nipc_error_t;
101
102
+/* ------------------------------------------------------------------ */
103
+/* Shared cgroups/apps lookup enums */
104
+/* ------------------------------------------------------------------ */
105
+
106
+typedef enum {
107
+ NIPC_ORCHESTRATOR_UNKNOWN = 0,
108
+ NIPC_ORCHESTRATOR_SYSTEMD = 1,
109
+ NIPC_ORCHESTRATOR_DOCKER = 2,
110
+ NIPC_ORCHESTRATOR_K8S = 3,
111
+ NIPC_ORCHESTRATOR_KVM = 4,
112
+ NIPC_ORCHESTRATOR_LXC = 5,
113
+ NIPC_ORCHESTRATOR_PODMAN = 6,
114
+ NIPC_ORCHESTRATOR_NSPAWN = 7,
115
+} nipc_orchestrator_t;
116
+
117
+typedef enum {
118
+ NIPC_CGROUP_LOOKUP_KNOWN = 0,
119
+ NIPC_CGROUP_LOOKUP_UNKNOWN_RETRY_LATER = 1,
120
+ NIPC_CGROUP_LOOKUP_UNKNOWN_PERMANENT = 2,
121
+} nipc_cgroup_lookup_status_t;
122
+
123
+typedef enum {
124
+ NIPC_PID_LOOKUP_KNOWN = 0,
125
+ NIPC_PID_LOOKUP_UNKNOWN = 1,
126
+} nipc_pid_lookup_status_t;
127
+
128
+typedef enum {
129
+ NIPC_APPS_CGROUP_KNOWN = 0,
130
+ NIPC_APPS_CGROUP_UNKNOWN_RETRY_LATER = 1,
131
+ NIPC_APPS_CGROUP_UNKNOWN_PERMANENT = 2,
132
+ NIPC_APPS_CGROUP_HOST_ROOT = 3,
133
+} nipc_apps_cgroup_status_t;
134
+
135
/* ------------------------------------------------------------------ */
136
/* Outer message header (32 bytes) */
137
/* ------------------------------------------------------------------ */
@@ -306,6 +344,11 @@ typedef struct {
344
uint32_t len; /* length excluding the NUL */
345
} nipc_str_view_t;
346
347
+typedef struct {
348
+ nipc_str_view_t key;
349
+ nipc_str_view_t value;
350
+} nipc_lookup_label_view_t;
351
+
352
/*
353
* Per-item view -- ephemeral, borrows the payload buffer.
354
* Valid only while the payload buffer is alive.
@@ -408,6 +451,200 @@ nipc_error_t nipc_cgroups_builder_add(nipc_cgroups_builder_t *b,
451
*/
452
size_t nipc_cgroups_builder_finish(nipc_cgroups_builder_t *b);
453
454
+/* ------------------------------------------------------------------ */
455
+/* Cgroups/apps lookup codecs */
456
+/* ------------------------------------------------------------------ */
457
+
458
+#define NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE 16u
459
+#define NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE 16u
460
+#define NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE 28u
461
+#define NIPC_APPS_LOOKUP_REQ_HDR_SIZE 16u
462
+#define NIPC_APPS_LOOKUP_RESP_HDR_SIZE 16u
463
+#define NIPC_APPS_LOOKUP_ITEM_HDR_SIZE 60u
464
+#define NIPC_LOOKUP_DIR_ENTRY_SIZE 8u
465
+#define NIPC_LOOKUP_LABEL_ENTRY_SIZE 16u
466
+#define NIPC_APPS_LOOKUP_KEY_SIZE 8u
467
+
468
+typedef struct {
469
+ uint32_t offset;
470
+ uint32_t length;
471
+} nipc_lookup_dir_entry_t;
472
+
473
+typedef struct {
474
+ uint32_t key_offset;
475
+ uint32_t key_length;
476
+ uint32_t value_offset;
477
+ uint32_t value_length;
478
+} nipc_lookup_label_entry_t;
479
+
480
+typedef struct {
481
+ const uint8_t *_payload;
482
+ size_t _payload_len;
483
+ uint32_t item_count;
484
+} nipc_cgroups_lookup_req_view_t;
485
+
486
+typedef struct {
487
+ nipc_str_view_t path;
488
+} nipc_cgroups_lookup_req_item_t;
489
+
490
+typedef struct {
491
+ uint16_t layout_version;
492
+ uint16_t flags;
493
+ uint32_t item_count;
494
+ uint64_t generation;
495
+ const uint8_t *_payload;
496
+ size_t _payload_len;
497
+} nipc_cgroups_lookup_resp_view_t;
498
+
499
+typedef struct {
500
+ uint16_t status;
501
+ uint16_t orchestrator;
502
+ nipc_str_view_t path;
503
+ nipc_str_view_t name;
504
+ uint16_t label_count;
505
+ const uint8_t *_item;
506
+ uint32_t _item_len;
507
+ uint32_t _label_table_offset;
508
+} nipc_cgroups_lookup_item_view_t;
509
+
510
+typedef struct {
511
+ uint8_t *buf;
512
+ size_t buf_len;
513
+ uint64_t generation;
514
+ uint32_t item_count;
515
+ uint32_t max_items;
516
+ nipc_error_t error;
517
+ size_t data_offset;
518
+} nipc_cgroups_lookup_builder_t;
519
+
520
+typedef struct {
521
+ const uint8_t *_payload;
522
+ size_t _payload_len;
523
+ uint32_t item_count;
524
+} nipc_apps_lookup_req_view_t;
525
+
526
+typedef struct {
527
+ uint32_t pid;
528
+} nipc_apps_lookup_req_item_t;
529
+
530
+typedef struct {
531
+ uint16_t layout_version;
532
+ uint16_t flags;
533
+ uint32_t item_count;
534
+ uint64_t generation;
535
+ const uint8_t *_payload;
536
+ size_t _payload_len;
537
+} nipc_apps_lookup_resp_view_t;
538
+
539
+typedef struct {
540
+ uint16_t status;
541
+ uint16_t orchestrator;
542
+ uint16_t cgroup_status;
543
+ uint32_t pid;
544
+ uint32_t ppid;
545
+ uint32_t uid;
546
+ uint64_t starttime;
547
+ nipc_str_view_t comm;
548
+ nipc_str_view_t cgroup_path;
549
+ nipc_str_view_t cgroup_name;
550
+ uint16_t label_count;
551
+ const uint8_t *_item;
552
+ uint32_t _item_len;
553
+ uint32_t _label_table_offset;
554
+} nipc_apps_lookup_item_view_t;
555
+
556
+typedef struct {
557
+ uint8_t *buf;
558
+ size_t buf_len;
559
+ uint64_t generation;
560
+ uint32_t item_count;
561
+ uint32_t max_items;
562
+ nipc_error_t error;
563
+ size_t data_offset;
564
+} nipc_apps_lookup_builder_t;
565
+
566
+size_t nipc_cgroups_lookup_req_encode(const nipc_str_view_t *paths,
567
+ uint32_t item_count,
568
+ void *buf, size_t buf_len);
569
+nipc_error_t nipc_cgroups_lookup_req_decode(const void *buf, size_t buf_len,
570
+ nipc_cgroups_lookup_req_view_t *out);
571
+nipc_error_t nipc_cgroups_lookup_req_item(
572
+ const nipc_cgroups_lookup_req_view_t *view,
573
+ uint32_t index,
574
+ nipc_cgroups_lookup_req_item_t *out);
575
+
576
+nipc_error_t nipc_cgroups_lookup_resp_decode(const void *buf, size_t buf_len,
577
+ nipc_cgroups_lookup_resp_view_t *out);
578
+nipc_error_t nipc_cgroups_lookup_resp_item(
579
+ const nipc_cgroups_lookup_resp_view_t *view,
580
+ uint32_t index,
581
+ nipc_cgroups_lookup_item_view_t *out);
582
+nipc_error_t nipc_cgroups_lookup_item_label(
583
+ const nipc_cgroups_lookup_item_view_t *item,
584
+ uint32_t index,
585
+ nipc_lookup_label_view_t *out);
586
+
587
+void nipc_cgroups_lookup_builder_init(nipc_cgroups_lookup_builder_t *b,
588
+ void *buf, size_t buf_len,
589
+ uint32_t max_items,
590
+ uint64_t generation);
591
+void nipc_cgroups_lookup_builder_set_generation(nipc_cgroups_lookup_builder_t *b,
592
+ uint64_t generation);
593
+uint32_t nipc_cgroups_lookup_builder_estimate_max_items(size_t buf_len);
594
+nipc_error_t nipc_cgroups_lookup_builder_add(
595
+ nipc_cgroups_lookup_builder_t *b,
596
+ uint16_t status,
597
+ uint16_t orchestrator,
598
+ const char *path, uint32_t path_len,
599
+ const char *name, uint32_t name_len,
600
+ const nipc_lookup_label_view_t *labels,
601
+ uint16_t label_count);
602
+size_t nipc_cgroups_lookup_builder_finish(nipc_cgroups_lookup_builder_t *b);
603
+
604
+size_t nipc_apps_lookup_req_encode(const uint32_t *pids,
605
+ uint32_t item_count,
606
+ void *buf, size_t buf_len);
607
+nipc_error_t nipc_apps_lookup_req_decode(const void *buf, size_t buf_len,
608
+ nipc_apps_lookup_req_view_t *out);
609
+nipc_error_t nipc_apps_lookup_req_item(
610
+ const nipc_apps_lookup_req_view_t *view,
611
+ uint32_t index,
612
+ nipc_apps_lookup_req_item_t *out);
613
+
614
+nipc_error_t nipc_apps_lookup_resp_decode(const void *buf, size_t buf_len,
615
+ nipc_apps_lookup_resp_view_t *out);
616
+nipc_error_t nipc_apps_lookup_resp_item(
617
+ const nipc_apps_lookup_resp_view_t *view,
618
+ uint32_t index,
619
+ nipc_apps_lookup_item_view_t *out);
620
+nipc_error_t nipc_apps_lookup_item_label(
621
+ const nipc_apps_lookup_item_view_t *item,
622
+ uint32_t index,
623
+ nipc_lookup_label_view_t *out);
624
+
625
+void nipc_apps_lookup_builder_init(nipc_apps_lookup_builder_t *b,
626
+ void *buf, size_t buf_len,
627
+ uint32_t max_items,
628
+ uint64_t generation);
629
+void nipc_apps_lookup_builder_set_generation(nipc_apps_lookup_builder_t *b,
630
+ uint64_t generation);
631
+uint32_t nipc_apps_lookup_builder_estimate_max_items(size_t buf_len);
632
+nipc_error_t nipc_apps_lookup_builder_add(
633
+ nipc_apps_lookup_builder_t *b,
634
+ uint16_t status,
635
+ uint16_t cgroup_status,
636
+ uint16_t orchestrator,
637
+ uint32_t pid,
638
+ uint32_t ppid,
639
+ uint32_t uid,
640
+ uint64_t starttime,
641
+ const char *comm, uint32_t comm_len,
642
+ const char *cgroup_path, uint32_t cgroup_path_len,
643
+ const char *cgroup_name, uint32_t cgroup_name_len,
644
+ const nipc_lookup_label_view_t *labels,
645
+ uint16_t label_count);
646
+size_t nipc_apps_lookup_builder_finish(nipc_apps_lookup_builder_t *b);
647
+
648
/* ------------------------------------------------------------------ */
649
/* INCREMENT codec (8 bytes) */
650
/* ------------------------------------------------------------------ */
@@ -502,6 +739,26 @@ nipc_error_t nipc_dispatch_cgroups_snapshot(
739
uint32_t max_items,
740
nipc_cgroups_handler_fn handler, void *user);
741
742
+typedef bool (*nipc_cgroups_lookup_handler_fn)(
743
+ void *user,
744
+ const nipc_cgroups_lookup_req_view_t *request,
745
+ nipc_cgroups_lookup_builder_t *builder);
746
+
747
+nipc_error_t nipc_dispatch_cgroups_lookup(
748
+ const uint8_t *req, size_t req_len,
749
+ uint8_t *resp, size_t resp_size, size_t *resp_len,
750
+ nipc_cgroups_lookup_handler_fn handler, void *user);
751
+
752
+typedef bool (*nipc_apps_lookup_handler_fn)(
753
+ void *user,
754
+ const nipc_apps_lookup_req_view_t *request,
755
+ nipc_apps_lookup_builder_t *builder);
756
+
757
+nipc_error_t nipc_dispatch_apps_lookup(
758
+ const uint8_t *req, size_t req_len,
759
+ uint8_t *resp, size_t resp_size, size_t *resp_len,
760
+ nipc_apps_lookup_handler_fn handler, void *user);
761
+
762
/* ------------------------------------------------------------------ */
763
/* Utility: 8-byte alignment */
764
/* ------------------------------------------------------------------ */
src/libnetdata/netipc/include/netipc/netipc_service.h
+45
-1
@@ -211,6 +211,18 @@ nipc_error_t nipc_client_call_cgroups_snapshot(
211
nipc_client_ctx_t *ctx,
212
nipc_cgroups_resp_view_t *view_out);
213
214
+nipc_error_t nipc_client_call_cgroups_lookup(
215
+ nipc_client_ctx_t *ctx,
216
+ const nipc_str_view_t *paths,
217
+ uint32_t path_count,
218
+ nipc_cgroups_lookup_resp_view_t *view_out);
219
+
220
+nipc_error_t nipc_client_call_apps_lookup(
221
+ nipc_client_ctx_t *ctx,
222
+ const uint32_t *pids,
223
+ uint32_t pid_count,
224
+ nipc_apps_lookup_resp_view_t *view_out);
225
+
226
/* ------------------------------------------------------------------ */
227
/* Managed server */
228
/* ------------------------------------------------------------------ */
@@ -235,6 +247,22 @@ typedef struct {
247
void *user;
248
} nipc_cgroups_service_handler_t;
249
250
+typedef struct {
251
+ nipc_cgroups_lookup_handler_fn handle;
252
+ void *user;
253
+} nipc_cgroups_lookup_service_handler_t;
254
+
255
+typedef struct {
256
+ nipc_apps_lookup_handler_fn handle;
257
+ void *user;
258
+} nipc_apps_lookup_service_handler_t;
259
+
260
+typedef union {
261
+ nipc_cgroups_service_handler_t cgroups_snapshot;
262
+ nipc_cgroups_lookup_service_handler_t cgroups_lookup;
263
+ nipc_apps_lookup_service_handler_t apps_lookup;
264
+} nipc_server_typed_handler_t;
265
+
266
typedef struct nipc_managed_server nipc_managed_server_t;
267
268
/* Per-session context for multi-client server */
@@ -269,7 +297,7 @@ struct nipc_managed_server {
297
/* Callback */
298
nipc_server_handler_fn handler;
299
void *handler_user;
272
- nipc_cgroups_service_handler_t service_handler;
300
+ nipc_server_typed_handler_t typed_handler;
301
uint16_t expected_method_code;
302
uint32_t learned_request_payload_bytes;
303
uint32_t learned_response_payload_bytes;
@@ -318,6 +346,22 @@ nipc_error_t nipc_server_init_typed(nipc_managed_server_t *server,
346
int worker_count,
347
const nipc_cgroups_service_handler_t *service_handler);
348
349
+nipc_error_t nipc_server_init_cgroups_lookup(
350
+ nipc_managed_server_t *server,
351
+ const char *run_dir,
352
+ const char *service_name,
353
+ const nipc_server_config_t *config,
354
+ int worker_count,
355
+ const nipc_cgroups_lookup_service_handler_t *service_handler);
356
+
357
+nipc_error_t nipc_server_init_apps_lookup(
358
+ nipc_managed_server_t *server,
359
+ const char *run_dir,
360
+ const char *service_name,
361
+ const nipc_server_config_t *config,
362
+ int worker_count,
363
+ const nipc_apps_lookup_service_handler_t *service_handler);
364
+
365
#ifdef NIPC_INTERNAL_TESTING
366
/*
367
* Internal compatibility entrypoint for repo tests and benchmarks that
src/libnetdata/netipc/src/protocol/netipc_protocol.c
+1
-505
@@ -6,18 +6,7 @@
6
* No endianness conversion — both peers share host byte order.
7
*/
8
9
-#include "netipc/netipc_protocol.h"
10
-#include <stddef.h>
11
-#include <string.h>
12
-
13
-/*
14
- * Safe multiplication check: returns true if count * entry_size would
15
- * overflow size_t. Portable across 32-bit and 64-bit without triggering
16
- * -Wtype-limits.
17
- */
18
-static inline bool mul_would_overflow(size_t count, size_t entry_size) {
19
- return entry_size != 0 && count > SIZE_MAX / entry_size;
20
-}
9
+#include "netipc_protocol_internal.h"
10
11
/* ------------------------------------------------------------------ */
12
/* Compile-time layout assertions */
@@ -87,36 +76,6 @@ _Static_assert(offsetof(nipc_hello_ack_t, agreed_packet_size) == 32, "");
76
_Static_assert(offsetof(nipc_hello_ack_t, _reserved) == 36, "");
77
_Static_assert(offsetof(nipc_hello_ack_t, session_id) == 40, "");
78
90
-/* Cgroups snapshot response header (24 bytes) */
91
-_Static_assert(sizeof(nipc_cgroups_resp_header_t) == 24,
92
- "nipc_cgroups_resp_header_t must be 24 bytes");
93
-_Static_assert(offsetof(nipc_cgroups_resp_header_t, layout_version) == 0, "");
94
-_Static_assert(offsetof(nipc_cgroups_resp_header_t, flags) == 2, "");
95
-_Static_assert(offsetof(nipc_cgroups_resp_header_t, item_count) == 4, "");
96
-_Static_assert(offsetof(nipc_cgroups_resp_header_t, systemd_enabled) == 8, "");
97
-_Static_assert(offsetof(nipc_cgroups_resp_header_t, reserved) == 12, "");
98
-_Static_assert(offsetof(nipc_cgroups_resp_header_t, generation) == 16, "");
99
-
100
-/* Cgroups item wire header (internal, 32 bytes) */
101
-typedef struct {
102
- uint16_t layout_version;
103
- uint16_t flags;
104
- uint32_t hash;
105
- uint32_t options;
106
- uint32_t enabled;
107
- uint32_t name_offset;
108
- uint32_t name_length;
109
- uint32_t path_offset;
110
- uint32_t path_length;
111
-} nipc_cgroups_item_wire_t;
112
-
113
-_Static_assert(sizeof(nipc_cgroups_item_wire_t) == 32,
114
- "nipc_cgroups_item_wire_t must be 32 bytes");
115
-
116
-/* Cgroups request (4 bytes) */
117
-_Static_assert(sizeof(nipc_cgroups_req_t) == 4,
118
- "nipc_cgroups_req_t must be 4 bytes");
119
-
79
/* ------------------------------------------------------------------ */
80
/* Outer message header (32 bytes) */
81
/* ------------------------------------------------------------------ */
@@ -403,466 +362,3 @@ nipc_error_t nipc_hello_ack_decode(const void *buf, size_t buf_len,
362
363
return NIPC_OK;
364
}
406
-
407
-/* ------------------------------------------------------------------ */
408
-/* Cgroups snapshot request (4 bytes) */
409
-/* ------------------------------------------------------------------ */
410
-
411
-size_t nipc_cgroups_req_encode(const nipc_cgroups_req_t *r,
412
- void *buf, size_t buf_len) {
413
- if (buf_len < sizeof(nipc_cgroups_req_t))
414
- return 0;
415
-
416
- memcpy(buf, r, sizeof(nipc_cgroups_req_t));
417
- return sizeof(nipc_cgroups_req_t);
418
-}
419
-
420
-nipc_error_t nipc_cgroups_req_decode(const void *buf, size_t buf_len,
421
- nipc_cgroups_req_t *out) {
422
- if (buf_len < sizeof(nipc_cgroups_req_t))
423
- return NIPC_ERR_TRUNCATED;
424
-
425
- memcpy(out, buf, sizeof(nipc_cgroups_req_t));
426
-
427
- if (out->layout_version != 1)
428
- return NIPC_ERR_BAD_LAYOUT;
429
- if (out->flags != 0)
430
- return NIPC_ERR_BAD_LAYOUT;
431
-
432
- return NIPC_OK;
433
-}
434
-
435
-/* ------------------------------------------------------------------ */
436
-/* Cgroups snapshot response decode */
437
-/* ------------------------------------------------------------------ */
438
-
439
-nipc_error_t nipc_cgroups_resp_decode(const void *buf, size_t buf_len,
440
- nipc_cgroups_resp_view_t *out) {
441
- if (buf_len < NIPC_CGROUPS_RESP_HDR_SIZE)
442
- return NIPC_ERR_TRUNCATED;
443
-
444
- nipc_cgroups_resp_header_t hdr;
445
- memcpy(&hdr, buf, sizeof(hdr));
446
-
447
- if (hdr.layout_version != 1)
448
- return NIPC_ERR_BAD_LAYOUT;
449
- if (hdr.flags != 0)
450
- return NIPC_ERR_BAD_LAYOUT;
451
- if (hdr.reserved != 0)
452
- return NIPC_ERR_BAD_LAYOUT;
453
-
454
- out->layout_version = hdr.layout_version;
455
- out->flags = hdr.flags;
456
- out->item_count = hdr.item_count;
457
- out->systemd_enabled = hdr.systemd_enabled;
458
- out->generation = hdr.generation;
459
-
460
- /* Validate directory fits (with overflow check) */
461
- if (mul_would_overflow((size_t)out->item_count, NIPC_CGROUPS_DIR_ENTRY_SIZE))
462
- return NIPC_ERR_BAD_ITEM_COUNT;
463
- size_t dir_size = (size_t)out->item_count * NIPC_CGROUPS_DIR_ENTRY_SIZE;
464
- size_t dir_end = NIPC_CGROUPS_RESP_HDR_SIZE + dir_size;
465
- if (dir_end > buf_len)
466
- return NIPC_ERR_TRUNCATED;
467
-
468
- size_t packed_area_len = buf_len - dir_end;
469
-
470
- /* Validate each directory entry */
471
- const uint8_t *dir = (const uint8_t *)buf + NIPC_CGROUPS_RESP_HDR_SIZE;
472
- for (uint32_t i = 0; i < out->item_count; i++) {
473
- nipc_batch_entry_t entry;
474
- memcpy(&entry, dir + i * sizeof(entry), sizeof(entry));
475
-
476
- if (entry.offset % NIPC_ALIGNMENT != 0)
477
- return NIPC_ERR_BAD_ALIGNMENT;
478
- if ((uint64_t)entry.offset + entry.length > packed_area_len)
479
- return NIPC_ERR_OUT_OF_BOUNDS;
480
- if (entry.length < NIPC_CGROUPS_ITEM_HDR_SIZE)
481
- return NIPC_ERR_TRUNCATED;
482
- }
483
-
484
- out->_payload = (const uint8_t *)buf;
485
- out->_payload_len = buf_len;
486
- return NIPC_OK;
487
-}
488
-
489
-nipc_error_t nipc_cgroups_resp_item(const nipc_cgroups_resp_view_t *view,
490
- uint32_t index,
491
- nipc_cgroups_item_view_t *out) {
492
- if (index >= view->item_count)
493
- return NIPC_ERR_OUT_OF_BOUNDS;
494
-
495
- /* Overflow already checked in nipc_cgroups_resp_decode, but
496
- * guard defensively since this is a public API. */
497
- if (mul_would_overflow((size_t)view->item_count, NIPC_CGROUPS_DIR_ENTRY_SIZE))
498
- return NIPC_ERR_BAD_ITEM_COUNT;
499
-
500
- size_t dir_start = NIPC_CGROUPS_RESP_HDR_SIZE;
501
- size_t dir_size = (size_t)view->item_count * NIPC_CGROUPS_DIR_ENTRY_SIZE;
502
- size_t packed_area_start = dir_start + dir_size;
503
-
504
- /* Read directory entry */
505
- nipc_batch_entry_t dir_entry;
506
- memcpy(&dir_entry,
507
- view->_payload + dir_start + index * sizeof(dir_entry),
508
- sizeof(dir_entry));
509
-
510
- const uint8_t *item = view->_payload + packed_area_start + dir_entry.offset;
511
- uint32_t item_len = dir_entry.length;
512
-
513
- /* Read the 32-byte item wire header in one copy */
514
- nipc_cgroups_item_wire_t wire;
515
- memcpy(&wire, item, sizeof(wire));
516
-
517
- if (wire.layout_version != 1)
518
- return NIPC_ERR_BAD_LAYOUT;
519
- if (wire.flags != 0)
520
- return NIPC_ERR_BAD_LAYOUT;
521
-
522
- /* Validate name string */
523
- if (wire.name_offset < NIPC_CGROUPS_ITEM_HDR_SIZE)
524
- return NIPC_ERR_OUT_OF_BOUNDS;
525
- if ((uint64_t)wire.name_offset + wire.name_length + 1 > item_len)
526
- return NIPC_ERR_OUT_OF_BOUNDS;
527
- if (item[wire.name_offset + wire.name_length] != '\0')
528
- return NIPC_ERR_MISSING_NUL;
529
-
530
- /* Validate path string */
531
- if (wire.path_offset < NIPC_CGROUPS_ITEM_HDR_SIZE)
532
- return NIPC_ERR_OUT_OF_BOUNDS;
533
- if ((uint64_t)wire.path_offset + wire.path_length + 1 > item_len)
534
- return NIPC_ERR_OUT_OF_BOUNDS;
535
- if (item[wire.path_offset + wire.path_length] != '\0')
536
- return NIPC_ERR_MISSING_NUL;
537
-
538
- /* Reject overlapping name and path regions (including NUL) */
539
- {
540
- uint64_t name_start = wire.name_offset;
541
- uint64_t name_end = name_start + wire.name_length + 1;
542
- uint64_t path_start = wire.path_offset;
543
- uint64_t path_end = path_start + wire.path_length + 1;
544
- if (name_start < path_end && path_start < name_end)
545
- return NIPC_ERR_BAD_LAYOUT;
546
- }
547
-
548
- out->layout_version = wire.layout_version;
549
- out->flags = wire.flags;
550
- out->hash = wire.hash;
551
- out->options = wire.options;
552
- out->enabled = wire.enabled;
553
- out->name.ptr = (const char *)(item + wire.name_offset);
554
- out->name.len = wire.name_length;
555
- out->path.ptr = (const char *)(item + wire.path_offset);
556
- out->path.len = wire.path_length;
557
-
558
- return NIPC_OK;
559
-}
560
-
561
-/* ------------------------------------------------------------------ */
562
-/* Cgroups snapshot response builder */
563
-/* */
564
-/* Layout during building (max_items directory slots reserved): */
565
-/* [24-byte header space] [max_items*8 directory] [packed items] */
566
-/* */
567
-/* Layout after finish (compacted to actual item_count): */
568
-/* [24-byte header] [item_count*8 directory] [packed items] */
569
-/* */
570
-/* If item_count < max_items, finish() shifts packed data left and */
571
-/* adjusts directory offsets accordingly. */
572
-/* ------------------------------------------------------------------ */
573
-
574
-void nipc_cgroups_builder_init(nipc_cgroups_builder_t *b,
575
- void *buf, size_t buf_len,
576
- uint32_t max_items,
577
- uint32_t systemd_enabled,
578
- uint64_t generation) {
579
- b->buf = (uint8_t *)buf;
580
- b->buf_len = buf_len;
581
- b->systemd_enabled = systemd_enabled;
582
- b->generation = generation;
583
- b->item_count = 0;
584
- b->max_items = max_items;
585
- b->error = NIPC_OK;
586
-
587
- /* Packed item data starts after reserved directory */
588
- b->data_offset = NIPC_CGROUPS_RESP_HDR_SIZE +
589
- (size_t)max_items * NIPC_CGROUPS_DIR_ENTRY_SIZE;
590
-}
591
-
592
-void nipc_cgroups_builder_set_header(nipc_cgroups_builder_t *b,
593
- uint32_t systemd_enabled,
594
- uint64_t generation) {
595
- b->systemd_enabled = systemd_enabled;
596
- b->generation = generation;
597
-}
598
-
599
-uint32_t nipc_cgroups_builder_estimate_max_items(size_t buf_len) {
600
- if (buf_len <= NIPC_CGROUPS_RESP_HDR_SIZE)
601
- return 0;
602
-
603
- size_t min_aligned_item = nipc_align8(NIPC_CGROUPS_ITEM_HDR_SIZE + 2u);
604
- return (uint32_t)((buf_len - NIPC_CGROUPS_RESP_HDR_SIZE) /
605
- (NIPC_CGROUPS_DIR_ENTRY_SIZE + min_aligned_item));
606
-}
607
-
608
-nipc_error_t nipc_cgroups_builder_add(nipc_cgroups_builder_t *b,
609
- uint32_t hash,
610
- uint32_t options,
611
- uint32_t enabled,
612
- const char *name, uint32_t name_len,
613
- const char *path, uint32_t path_len) {
614
- if (b->item_count >= b->max_items) {
615
- b->error = NIPC_ERR_OVERFLOW;
616
- return NIPC_ERR_OVERFLOW;
617
- }
618
-
619
- /* Align item start to 8 bytes */
620
- size_t item_start = nipc_align8(b->data_offset);
621
-
622
- /* Item payload: 32-byte header + name + NUL + path + NUL */
623
- size_t item_size = NIPC_CGROUPS_ITEM_HDR_SIZE +
624
- (size_t)name_len + 1 +
625
- (size_t)path_len + 1;
626
-
627
- if (item_start + item_size > b->buf_len) {
628
- b->error = NIPC_ERR_OVERFLOW;
629
- return NIPC_ERR_OVERFLOW;
630
- }
631
-
632
- /* Zero alignment padding */
633
- if (item_start > b->data_offset)
634
- memset(b->buf + b->data_offset, 0, item_start - b->data_offset);
635
-
636
- uint8_t *item = b->buf + item_start;
637
-
638
- /* Write item header as a single struct copy */
639
- nipc_cgroups_item_wire_t wire = {
640
- .layout_version = 1,
641
- .flags = 0,
642
- .hash = hash,
643
- .options = options,
644
- .enabled = enabled,
645
- .name_offset = NIPC_CGROUPS_ITEM_HDR_SIZE,
646
- .name_length = name_len,
647
- .path_offset = NIPC_CGROUPS_ITEM_HDR_SIZE + name_len + 1,
648
- .path_length = path_len,
649
- };
650
- memcpy(item, &wire, sizeof(wire));
651
-
652
- /* Write strings with NUL terminators */
653
- memcpy(item + wire.name_offset, name, name_len);
654
- item[wire.name_offset + name_len] = '\0';
655
- memcpy(item + wire.path_offset, path, path_len);
656
- item[wire.path_offset + path_len] = '\0';
657
-
658
- /* Write directory entry (absolute offset stored temporarily) */
659
- nipc_batch_entry_t dir_entry = {
660
- .offset = (uint32_t)item_start,
661
- .length = (uint32_t)item_size,
662
- };
663
- size_t dir_pos = NIPC_CGROUPS_RESP_HDR_SIZE +
664
- (size_t)b->item_count * NIPC_CGROUPS_DIR_ENTRY_SIZE;
665
- memcpy(b->buf + dir_pos, &dir_entry, sizeof(dir_entry));
666
-
667
- b->data_offset = item_start + item_size;
668
- b->item_count++;
669
- return NIPC_OK;
670
-}
671
-
672
-size_t nipc_cgroups_builder_finish(nipc_cgroups_builder_t *b) {
673
- uint8_t *p = b->buf;
674
-
675
- nipc_cgroups_resp_header_t hdr = {
676
- .layout_version = 1,
677
- .flags = 0,
678
- .item_count = b->item_count,
679
- .systemd_enabled = b->systemd_enabled,
680
- .reserved = 0,
681
- .generation = b->generation,
682
- };
683
-
684
- if (b->item_count == 0) {
685
- memcpy(p, &hdr, sizeof(hdr));
686
- return NIPC_CGROUPS_RESP_HDR_SIZE;
687
- }
688
-
689
- /* Where the decoder expects packed data to start */
690
- size_t final_packed_start = NIPC_CGROUPS_RESP_HDR_SIZE +
691
- (size_t)b->item_count * NIPC_CGROUPS_DIR_ENTRY_SIZE;
692
-
693
- /* Read the first directory entry to find where packed data actually begins */
694
- nipc_batch_entry_t first_entry;
695
- memcpy(&first_entry, p + NIPC_CGROUPS_RESP_HDR_SIZE, sizeof(first_entry));
696
- uint32_t first_item_abs = first_entry.offset;
697
-
698
- /* Guard against underflow if builder state is inconsistent */
699
- if (b->data_offset < first_item_abs) {
700
- hdr.item_count = 0;
701
- memcpy(p, &hdr, sizeof(hdr));
702
- return NIPC_CGROUPS_RESP_HDR_SIZE;
703
- }
704
-
705
- size_t packed_data_len = b->data_offset - first_item_abs;
706
-
707
- if (final_packed_start < first_item_abs) {
708
- memmove(p + final_packed_start, p + first_item_abs, packed_data_len);
709
- }
710
-
711
- /* Convert directory entries from absolute offsets to relative offsets */
712
- size_t dir_base = NIPC_CGROUPS_RESP_HDR_SIZE;
713
- for (uint32_t i = 0; i < b->item_count; i++) {
714
- size_t entry_pos = dir_base + (size_t)i * NIPC_CGROUPS_DIR_ENTRY_SIZE;
715
- nipc_batch_entry_t entry;
716
- memcpy(&entry, p + entry_pos, sizeof(entry));
717
- if (entry.offset < first_item_abs)
718
- continue; /* skip corrupted entry */
719
- entry.offset -= first_item_abs;
720
- memcpy(p + entry_pos, &entry, sizeof(entry));
721
- }
722
-
723
- /* Write snapshot header */
724
- memcpy(p, &hdr, sizeof(hdr));
725
-
726
- return final_packed_start + packed_data_len;
727
-}
728
-
729
-/* ------------------------------------------------------------------ */
730
-/* INCREMENT codec */
731
-/* ------------------------------------------------------------------ */
732
-
733
-size_t nipc_increment_encode(uint64_t value, void *buf, size_t buf_len) {
734
- if (buf_len < NIPC_INCREMENT_PAYLOAD_SIZE)
735
- return 0;
736
- memcpy(buf, &value, 8);
737
- return NIPC_INCREMENT_PAYLOAD_SIZE;
738
-}
739
-
740
-nipc_error_t nipc_increment_decode(const void *buf, size_t buf_len,
741
- uint64_t *value_out) {
742
- if (buf_len < NIPC_INCREMENT_PAYLOAD_SIZE)
743
- return NIPC_ERR_TRUNCATED;
744
- memcpy(value_out, buf, 8);
745
- return NIPC_OK;
746
-}
747
-
748
-/* ------------------------------------------------------------------ */
749
-/* STRING_REVERSE codec */
750
-/* ------------------------------------------------------------------ */
751
-
752
-size_t nipc_string_reverse_encode(const char *str, uint32_t str_len,
753
- void *buf, size_t buf_len) {
754
- /* Guard against size_t overflow only where uint32_t can exceed size_t. */
755
-#if SIZE_MAX <= UINT32_MAX
756
- if ((size_t)str_len > SIZE_MAX - (size_t)NIPC_STRING_REVERSE_HDR_SIZE - 1u)
757
- return 0;
758
-#endif
759
-
760
- size_t total = NIPC_STRING_REVERSE_HDR_SIZE + str_len + 1;
761
- if (buf_len < total)
762
- return 0;
763
-
764
- uint8_t *p = (uint8_t *)buf;
765
- uint32_t offset = NIPC_STRING_REVERSE_HDR_SIZE;
766
- memcpy(p + 0, &offset, 4);
767
- memcpy(p + 4, &str_len, 4);
768
- if (str_len > 0)
769
- memcpy(p + offset, str, str_len);
770
- p[offset + str_len] = '\0';
771
- return total;
772
-}
773
-
774
-nipc_error_t nipc_string_reverse_decode(const void *buf, size_t buf_len,
775
- nipc_string_reverse_view_t *view_out) {
776
- if (buf_len < NIPC_STRING_REVERSE_HDR_SIZE)
777
- return NIPC_ERR_TRUNCATED;
778
-
779
- const uint8_t *p = (const uint8_t *)buf;
780
- uint32_t str_offset, str_length;
781
- memcpy(&str_offset, p + 0, 4);
782
- memcpy(&str_length, p + 4, 4);
783
-
784
- if ((uint64_t)str_offset + str_length + 1 > buf_len)
785
- return NIPC_ERR_OUT_OF_BOUNDS;
786
-
787
- if (p[str_offset + str_length] != '\0')
788
- return NIPC_ERR_MISSING_NUL;
789
-
790
- view_out->str = (const char *)(p + str_offset);
791
- view_out->str_len = str_length;
792
- return NIPC_OK;
793
-}
794
-
795
-/* ------------------------------------------------------------------ */
796
-/* Server-side typed dispatch helpers */
797
-/* ------------------------------------------------------------------ */
798
-
799
-bool nipc_dispatch_increment(
800
- const uint8_t *req, size_t req_len,
801
- uint8_t *resp, size_t resp_size, size_t *resp_len,
802
- nipc_increment_handler_fn handler, void *user)
803
-{
804
- uint64_t value;
805
- if (nipc_increment_decode(req, req_len, &value) != NIPC_OK)
806
- return false;
807
-
808
- uint64_t result;
809
- if (!handler(user, value, &result))
810
- return false;
811
-
812
- *resp_len = nipc_increment_encode(result, resp, resp_size);
813
- return *resp_len > 0;
814
-}
815
-
816
-bool nipc_dispatch_string_reverse(
817
- const uint8_t *req, size_t req_len,
818
- uint8_t *resp, size_t resp_size, size_t *resp_len,
819
- nipc_string_reverse_handler_fn handler, void *user)
820
-{
821
- nipc_string_reverse_view_t view;
822
- if (nipc_string_reverse_decode(req, req_len, &view) != NIPC_OK)
823
- return false;
824
-
825
- /* Provide a scratch buffer for the handler's response string.
826
- * The handler writes the response string into it; we encode after. */
827
- uint32_t capacity = (resp_size > NIPC_STRING_REVERSE_HDR_SIZE + 1)
828
- ? (uint32_t)(resp_size - NIPC_STRING_REVERSE_HDR_SIZE - 1)
829
- : 0;
830
- char *scratch = (char *)(resp + NIPC_STRING_REVERSE_HDR_SIZE);
831
-
832
- uint32_t response_str_len = 0;
833
- if (!handler(user, view.str, view.str_len,
834
- scratch, capacity, &response_str_len))
835
- return false;
836
-
837
- /* Encode from the scratch area (already at the right offset) */
838
- *resp_len = nipc_string_reverse_encode(scratch, response_str_len,
839
- resp, resp_size);
840
- return *resp_len > 0;
841
-}
842
-
843
-nipc_error_t nipc_dispatch_cgroups_snapshot(
844
- const uint8_t *req, size_t req_len,
845
- uint8_t *resp, size_t resp_size, size_t *resp_len,
846
- uint32_t max_items,
847
- nipc_cgroups_handler_fn handler, void *user)
848
-{
849
- nipc_cgroups_req_t request;
850
- nipc_error_t err = nipc_cgroups_req_decode(req, req_len, &request);
851
- if (err != NIPC_OK)
852
- return err;
853
-
854
- nipc_cgroups_builder_t builder;
855
- nipc_cgroups_builder_init(&builder, resp, resp_size, max_items, 0, 0);
856
-
857
- if (!handler(user, &request, &builder)) {
858
- if (builder.error != NIPC_OK)
859
- return builder.error;
860
- return NIPC_ERR_HANDLER_FAILED;
861
- }
862
-
863
- if (builder.error != NIPC_OK)
864
- return builder.error;
865
-
866
- *resp_len = nipc_cgroups_builder_finish(&builder);
867
- return (*resp_len > 0) ? NIPC_OK : NIPC_ERR_OVERFLOW;
868
-}
src/libnetdata/netipc/src/protocol/netipc_protocol_apps_lookup.c
new
+547
@@ -0,0 +1,547 @@
1
+#include "netipc_protocol_apps_lookup_internal.h"
2
+
3
+_Static_assert(sizeof(nipc_apps_lookup_key_wire_t) == NIPC_APPS_LOOKUP_KEY_SIZE,
4
+ "apps lookup key must be 8 bytes");
5
+_Static_assert(offsetof(nipc_apps_lookup_key_wire_t, pid) == 0, "");
6
+_Static_assert(offsetof(nipc_apps_lookup_key_wire_t, reserved) == 4, "");
7
+
8
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, layout_version) == 0, "");
9
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, status) == 2, "");
10
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, orchestrator) == 4, "");
11
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, cgroup_status) == 6, "");
12
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, pid) == 8, "");
13
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, ppid) == 12, "");
14
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, uid) == 16, "");
15
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, reserved0) == 20, "");
16
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, starttime) == 24, "");
17
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, comm_offset) == 32, "");
18
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, comm_length) == 36, "");
19
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, cgroup_path_offset) == 40, "");
20
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, cgroup_path_length) == 44, "");
21
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, cgroup_name_offset) == 48, "");
22
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, cgroup_name_length) == 52, "");
23
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, label_count) == 56, "");
24
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, reserved1) == 58, "");
25
+_Static_assert(offsetof(nipc_apps_lookup_item_wire_t, reserved1) +
26
+ sizeof(((nipc_apps_lookup_item_wire_t *)0)->reserved1) ==
27
+ NIPC_APPS_LOOKUP_ITEM_HDR_SIZE,
28
+ "apps lookup fixed wire header must end at byte 60");
29
+_Static_assert(sizeof(nipc_apps_lookup_item_wire_t) >= NIPC_APPS_LOOKUP_ITEM_HDR_SIZE,
30
+ "apps lookup C struct must cover the fixed wire header");
31
+
32
+static nipc_error_t apps_lookup_validate_domains(uint16_t status,
33
+ uint16_t cgroup_status,
34
+ uint64_t comm_len) {
35
+ if (status != NIPC_PID_LOOKUP_KNOWN && status != NIPC_PID_LOOKUP_UNKNOWN)
36
+ return NIPC_ERR_BAD_LAYOUT;
37
+ if (cgroup_status != NIPC_APPS_CGROUP_KNOWN &&
38
+ cgroup_status != NIPC_APPS_CGROUP_UNKNOWN_RETRY_LATER &&
39
+ cgroup_status != NIPC_APPS_CGROUP_UNKNOWN_PERMANENT &&
40
+ cgroup_status != NIPC_APPS_CGROUP_HOST_ROOT)
41
+ return NIPC_ERR_BAD_LAYOUT;
42
+ if (comm_len > 15)
43
+ return NIPC_ERR_BAD_LAYOUT;
44
+ return NIPC_OK;
45
+}
46
+
47
+static nipc_error_t
48
+apps_lookup_validate_unknown(uint16_t orchestrator, uint16_t cgroup_status,
49
+ uint32_t ppid, uint32_t uid, uint64_t starttime,
50
+ uint64_t comm_len, uint64_t cgroup_path_len,
51
+ uint64_t cgroup_name_len, uint64_t label_count) {
52
+ if (orchestrator != 0 || cgroup_status != 0 || ppid != 0 ||
53
+ uid != NIPC_UID_UNSET || starttime != 0 || comm_len != 0 ||
54
+ cgroup_path_len != 0 || cgroup_name_len != 0 || label_count != 0)
55
+ return NIPC_ERR_BAD_LAYOUT;
56
+ return NIPC_OK;
57
+}
58
+
59
+static nipc_error_t
60
+apps_lookup_validate_known(uint16_t cgroup_status, uint16_t orchestrator,
61
+ uint64_t comm_len, uint64_t cgroup_path_len,
62
+ uint64_t cgroup_name_len, uint64_t label_count) {
63
+ if (comm_len == 0)
64
+ return NIPC_ERR_BAD_LAYOUT;
65
+
66
+ switch (cgroup_status) {
67
+ case NIPC_APPS_CGROUP_KNOWN:
68
+ if (cgroup_path_len == 0)
69
+ return NIPC_ERR_BAD_LAYOUT;
70
+ break;
71
+ case NIPC_APPS_CGROUP_UNKNOWN_RETRY_LATER:
72
+ if (orchestrator != 0 || cgroup_name_len != 0 || label_count != 0)
73
+ return NIPC_ERR_BAD_LAYOUT;
74
+ break;
75
+ case NIPC_APPS_CGROUP_UNKNOWN_PERMANENT:
76
+ if (cgroup_path_len == 0 || orchestrator != 0 || cgroup_name_len != 0 ||
77
+ label_count != 0)
78
+ return NIPC_ERR_BAD_LAYOUT;
79
+ break;
80
+ case NIPC_APPS_CGROUP_HOST_ROOT:
81
+ if (orchestrator != 0 || cgroup_path_len != 0 || cgroup_name_len != 0 ||
82
+ label_count != 0)
83
+ return NIPC_ERR_BAD_LAYOUT;
84
+ break;
85
+ default:
86
+ return NIPC_ERR_BAD_LAYOUT;
87
+ }
88
+ return NIPC_OK;
89
+}
90
+
91
+static nipc_error_t apps_lookup_validate_semantics(
92
+ uint16_t status, uint16_t cgroup_status, uint16_t orchestrator,
93
+ uint32_t ppid, uint32_t uid, uint64_t starttime, uint64_t comm_len,
94
+ uint64_t cgroup_path_len, uint64_t cgroup_name_len, uint64_t label_count) {
95
+ nipc_error_t err =
96
+ apps_lookup_validate_domains(status, cgroup_status, comm_len);
97
+ if (err != NIPC_OK)
98
+ return err;
99
+ if (status == NIPC_PID_LOOKUP_UNKNOWN)
100
+ return apps_lookup_validate_unknown(orchestrator, cgroup_status, ppid, uid,
101
+ starttime, comm_len, cgroup_path_len,
102
+ cgroup_name_len, label_count);
103
+ return apps_lookup_validate_known(cgroup_status, orchestrator, comm_len,
104
+ cgroup_path_len, cgroup_name_len,
105
+ label_count);
106
+}
107
+
108
+/* ------------------------------------------------------------------ */
109
+/* Apps lookup request */
110
+/* ------------------------------------------------------------------ */
111
+
112
+size_t nipc_apps_lookup_req_encode(const uint32_t *pids, uint32_t item_count,
113
+ void *buf, size_t buf_len) {
114
+ if (mul_would_overflow((size_t)item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
115
+ return 0;
116
+ if (mul_would_overflow((size_t)item_count, NIPC_APPS_LOOKUP_KEY_SIZE))
117
+ return 0;
118
+
119
+ size_t dir_size = (size_t)item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
120
+ size_t key_size = (size_t)item_count * NIPC_APPS_LOOKUP_KEY_SIZE;
121
+#if SIZE_MAX <= UINT32_MAX
122
+ if (dir_size > SIZE_MAX - NIPC_APPS_LOOKUP_REQ_HDR_SIZE)
123
+ return 0;
124
+#endif
125
+ size_t packed_start = NIPC_APPS_LOOKUP_REQ_HDR_SIZE + dir_size;
126
+#if SIZE_MAX <= UINT32_MAX
127
+ if (key_size > SIZE_MAX - packed_start)
128
+ return 0;
129
+#endif
130
+ if (buf_len < packed_start + key_size)
131
+ return 0;
132
+ if (item_count > 0 && !pids)
133
+ return 0;
134
+
135
+ uint8_t *p = (uint8_t *)buf;
136
+ for (uint32_t i = 0; i < item_count; i++) {
137
+ uint64_t key_offset = (uint64_t)i * NIPC_APPS_LOOKUP_KEY_SIZE;
138
+ if (key_offset > UINT32_MAX)
139
+ return 0;
140
+ nipc_lookup_dir_entry_t entry = {
141
+ .offset = (uint32_t)key_offset,
142
+ .length = NIPC_APPS_LOOKUP_KEY_SIZE,
143
+ };
144
+ nipc_apps_lookup_key_wire_t key = {
145
+ .pid = pids[i],
146
+ .reserved = 0,
147
+ };
148
+ memcpy(p + NIPC_APPS_LOOKUP_REQ_HDR_SIZE +
149
+ (size_t)i * NIPC_LOOKUP_DIR_ENTRY_SIZE,
150
+ &entry, sizeof(entry));
151
+ memcpy(p + packed_start + (size_t)i * NIPC_APPS_LOOKUP_KEY_SIZE, &key,
152
+ sizeof(key));
153
+ }
154
+
155
+ nipc_lookup_req_header_wire_t hdr = {
156
+ .layout_version = 1,
157
+ .flags = 0,
158
+ .item_count = item_count,
159
+ .reserved0 = 0,
160
+ .reserved1 = 0,
161
+ };
162
+ memcpy(p, &hdr, sizeof(hdr));
163
+ return packed_start + key_size;
164
+}
165
+
166
+nipc_error_t nipc_apps_lookup_req_decode(const void *buf, size_t buf_len,
167
+ nipc_apps_lookup_req_view_t *out) {
168
+ if (buf_len < NIPC_APPS_LOOKUP_REQ_HDR_SIZE)
169
+ return NIPC_ERR_TRUNCATED;
170
+
171
+ nipc_lookup_req_header_wire_t hdr;
172
+ memcpy(&hdr, buf, sizeof(hdr));
173
+ if (hdr.layout_version != 1 || hdr.flags != 0 || hdr.reserved0 != 0 ||
174
+ hdr.reserved1 != 0)
175
+ return NIPC_ERR_BAD_LAYOUT;
176
+
177
+ if (mul_would_overflow((size_t)hdr.item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE) ||
178
+ mul_would_overflow((size_t)hdr.item_count, NIPC_APPS_LOOKUP_KEY_SIZE))
179
+ return NIPC_ERR_BAD_ITEM_COUNT;
180
+
181
+ size_t dir_size = (size_t)hdr.item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
182
+ size_t dir_end = NIPC_APPS_LOOKUP_REQ_HDR_SIZE + dir_size;
183
+ if (dir_end > buf_len)
184
+ return NIPC_ERR_TRUNCATED;
185
+ size_t packed_area_len = buf_len - dir_end;
186
+ if (packed_area_len > UINT32_MAX)
187
+ return NIPC_ERR_BAD_ITEM_COUNT;
188
+
189
+ const uint8_t *p = (const uint8_t *)buf;
190
+ const uint8_t *dir = p + NIPC_APPS_LOOKUP_REQ_HDR_SIZE;
191
+ nipc_error_t err = nipc_lookup_validate_ordered_dir(
192
+ dir, hdr.item_count, (uint32_t)packed_area_len, 0, true,
193
+ NIPC_APPS_LOOKUP_KEY_SIZE);
194
+ if (err != NIPC_OK)
195
+ return err;
196
+
197
+ const uint8_t *packed = p + dir_end;
198
+ for (uint32_t i = 0; i < hdr.item_count; i++) {
199
+ nipc_lookup_dir_entry_t entry;
200
+ memcpy(&entry, dir + (size_t)i * NIPC_LOOKUP_DIR_ENTRY_SIZE, sizeof(entry));
201
+ nipc_apps_lookup_key_wire_t key;
202
+ memcpy(&key, packed + entry.offset, sizeof(key));
203
+ if (key.reserved != 0)
204
+ return NIPC_ERR_BAD_LAYOUT;
205
+ }
206
+
207
+ out->item_count = hdr.item_count;
208
+ out->_payload = p;
209
+ out->_payload_len = buf_len;
210
+ return NIPC_OK;
211
+}
212
+
213
+nipc_error_t nipc_apps_lookup_req_item(const nipc_apps_lookup_req_view_t *view,
214
+ uint32_t index,
215
+ nipc_apps_lookup_req_item_t *out) {
216
+ if (index >= view->item_count)
217
+ return NIPC_ERR_OUT_OF_BOUNDS;
218
+
219
+ /* Decode validates this, but item accessors are public and may be
220
+ * called with manually constructed views. */
221
+ if (mul_would_overflow((size_t)view->item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
222
+ return NIPC_ERR_BAD_ITEM_COUNT;
223
+
224
+ size_t dir_size = (size_t)view->item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
225
+ size_t dir_end = NIPC_APPS_LOOKUP_REQ_HDR_SIZE + dir_size;
226
+ const uint8_t *dir = view->_payload + NIPC_APPS_LOOKUP_REQ_HDR_SIZE;
227
+ const uint8_t *packed = view->_payload + dir_end;
228
+
229
+ nipc_lookup_dir_entry_t entry;
230
+ memcpy(&entry, dir + (size_t)index * NIPC_LOOKUP_DIR_ENTRY_SIZE,
231
+ sizeof(entry));
232
+ nipc_apps_lookup_key_wire_t key;
233
+ memcpy(&key, packed + entry.offset, sizeof(key));
234
+ out->pid = key.pid;
235
+ return NIPC_OK;
236
+}
237
+
238
+/* ------------------------------------------------------------------ */
239
+/* Apps lookup response */
240
+/* ------------------------------------------------------------------ */
241
+
242
+static nipc_error_t
243
+apps_lookup_decode_item_bytes(const uint8_t *item, uint32_t item_len,
244
+ nipc_apps_lookup_item_view_t *out) {
245
+ if (item_len < NIPC_APPS_LOOKUP_ITEM_HDR_SIZE)
246
+ return NIPC_ERR_TRUNCATED;
247
+
248
+ nipc_apps_lookup_item_wire_t wire;
249
+ memcpy(&wire, item, NIPC_APPS_LOOKUP_ITEM_HDR_SIZE);
250
+
251
+ if (wire.layout_version != 1 || wire.reserved0 != 0 || wire.reserved1 != 0)
252
+ return NIPC_ERR_BAD_LAYOUT;
253
+ nipc_error_t err = apps_lookup_validate_semantics(
254
+ wire.status, wire.cgroup_status, wire.orchestrator, wire.ppid, wire.uid,
255
+ wire.starttime, wire.comm_length, wire.cgroup_path_length,
256
+ wire.cgroup_name_length, wire.label_count);
257
+ if (err != NIPC_OK)
258
+ return err;
259
+
260
+ nipc_str_view_t comm, cgroup_path, cgroup_name;
261
+ uint64_t comm_end, path_end, name_end;
262
+ err = nipc_lookup_string_view(item, item_len, NIPC_APPS_LOOKUP_ITEM_HDR_SIZE,
263
+ wire.comm_offset, wire.comm_length, &comm,
264
+ &comm_end);
265
+ if (err != NIPC_OK)
266
+ return err;
267
+ err = nipc_lookup_string_view(
268
+ item, item_len, NIPC_APPS_LOOKUP_ITEM_HDR_SIZE, wire.cgroup_path_offset,
269
+ wire.cgroup_path_length, &cgroup_path, &path_end);
270
+ if (err != NIPC_OK)
271
+ return err;
272
+ err = nipc_lookup_string_view(
273
+ item, item_len, NIPC_APPS_LOOKUP_ITEM_HDR_SIZE, wire.cgroup_name_offset,
274
+ wire.cgroup_name_length, &cgroup_name, &name_end);
275
+ if (err != NIPC_OK)
276
+ return err;
277
+
278
+ if (nipc_lookup_ranges_overlap_u64(wire.comm_offset, comm_end,
279
+ wire.cgroup_path_offset, path_end) ||
280
+ nipc_lookup_ranges_overlap_u64(wire.comm_offset, comm_end,
281
+ wire.cgroup_name_offset, name_end) ||
282
+ nipc_lookup_ranges_overlap_u64(wire.cgroup_path_offset, path_end,
283
+ wire.cgroup_name_offset, name_end))
284
+ return NIPC_ERR_BAD_LAYOUT;
285
+
286
+ uint64_t fixed_end = comm_end;
287
+ if (path_end > fixed_end)
288
+ fixed_end = path_end;
289
+ if (name_end > fixed_end)
290
+ fixed_end = name_end;
291
+ uint32_t label_table_offset = 0;
292
+ err = nipc_lookup_validate_labels(
293
+ item, item_len, NIPC_APPS_LOOKUP_ITEM_HDR_SIZE, wire.label_count,
294
+ fixed_end, &label_table_offset);
295
+ if (err != NIPC_OK)
296
+ return err;
297
+
298
+ if (out) {
299
+ out->status = wire.status;
300
+ out->orchestrator = wire.orchestrator;
301
+ out->cgroup_status = wire.cgroup_status;
302
+ out->pid = wire.pid;
303
+ out->ppid = wire.ppid;
304
+ out->uid = wire.uid;
305
+ out->starttime = wire.starttime;
306
+ out->comm = comm;
307
+ out->cgroup_path = cgroup_path;
308
+ out->cgroup_name = cgroup_name;
309
+ out->label_count = wire.label_count;
310
+ out->_item = item;
311
+ out->_item_len = item_len;
312
+ out->_label_table_offset = label_table_offset;
313
+ }
314
+ return NIPC_OK;
315
+}
316
+
317
+nipc_error_t nipc_apps_lookup_resp_decode(const void *buf, size_t buf_len,
318
+ nipc_apps_lookup_resp_view_t *out) {
319
+ if (buf_len < NIPC_APPS_LOOKUP_RESP_HDR_SIZE)
320
+ return NIPC_ERR_TRUNCATED;
321
+
322
+ nipc_lookup_resp_header_wire_t hdr;
323
+ memcpy(&hdr, buf, sizeof(hdr));
324
+ if (hdr.layout_version != 1 || hdr.flags != 0)
325
+ return NIPC_ERR_BAD_LAYOUT;
326
+
327
+ if (mul_would_overflow((size_t)hdr.item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
328
+ return NIPC_ERR_BAD_ITEM_COUNT;
329
+ size_t dir_size = (size_t)hdr.item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
330
+ size_t dir_end = NIPC_APPS_LOOKUP_RESP_HDR_SIZE + dir_size;
331
+ if (dir_end > buf_len)
332
+ return NIPC_ERR_TRUNCATED;
333
+ size_t packed_area_len = buf_len - dir_end;
334
+ if (packed_area_len > UINT32_MAX)
335
+ return NIPC_ERR_BAD_ITEM_COUNT;
336
+
337
+ const uint8_t *p = (const uint8_t *)buf;
338
+ const uint8_t *dir = p + NIPC_APPS_LOOKUP_RESP_HDR_SIZE;
339
+ nipc_error_t err = nipc_lookup_validate_ordered_dir(
340
+ dir, hdr.item_count, (uint32_t)packed_area_len,
341
+ NIPC_APPS_LOOKUP_ITEM_HDR_SIZE, false, 0);
342
+ if (err != NIPC_OK)
343
+ return err;
344
+
345
+ const uint8_t *packed = p + dir_end;
346
+ for (uint32_t i = 0; i < hdr.item_count; i++) {
347
+ nipc_lookup_dir_entry_t entry;
348
+ memcpy(&entry, dir + (size_t)i * NIPC_LOOKUP_DIR_ENTRY_SIZE, sizeof(entry));
349
+ err = apps_lookup_decode_item_bytes(packed + entry.offset, entry.length,
350
+ NULL);
351
+ if (err != NIPC_OK)
352
+ return err;
353
+ }
354
+
355
+ out->layout_version = hdr.layout_version;
356
+ out->flags = hdr.flags;
357
+ out->item_count = hdr.item_count;
358
+ out->generation = hdr.generation;
359
+ out->_payload = p;
360
+ out->_payload_len = buf_len;
361
+ return NIPC_OK;
362
+}
363
+
364
+nipc_error_t
365
+nipc_apps_lookup_resp_item(const nipc_apps_lookup_resp_view_t *view,
366
+ uint32_t index, nipc_apps_lookup_item_view_t *out) {
367
+ if (index >= view->item_count)
368
+ return NIPC_ERR_OUT_OF_BOUNDS;
369
+
370
+ /* Decode validates this, but item accessors are public and may be
371
+ * called with manually constructed views. */
372
+ if (mul_would_overflow((size_t)view->item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
373
+ return NIPC_ERR_BAD_ITEM_COUNT;
374
+
375
+ size_t dir_size = (size_t)view->item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
376
+ size_t dir_end = NIPC_APPS_LOOKUP_RESP_HDR_SIZE + dir_size;
377
+ const uint8_t *dir = view->_payload + NIPC_APPS_LOOKUP_RESP_HDR_SIZE;
378
+ const uint8_t *packed = view->_payload + dir_end;
379
+ nipc_lookup_dir_entry_t entry;
380
+ memcpy(&entry, dir + (size_t)index * NIPC_LOOKUP_DIR_ENTRY_SIZE,
381
+ sizeof(entry));
382
+ return apps_lookup_decode_item_bytes(packed + entry.offset, entry.length,
383
+ out);
384
+}
385
+
386
+nipc_error_t
387
+nipc_apps_lookup_item_label(const nipc_apps_lookup_item_view_t *item,
388
+ uint32_t index, nipc_lookup_label_view_t *out) {
389
+ return nipc_lookup_label_at(item->_item, item->_item_len,
390
+ NIPC_APPS_LOOKUP_ITEM_HDR_SIZE, item->label_count,
391
+ item->_label_table_offset, index, out);
392
+}
393
+
394
+void nipc_apps_lookup_builder_init(nipc_apps_lookup_builder_t *b, void *buf,
395
+ size_t buf_len, uint32_t max_items,
396
+ uint64_t generation) {
397
+ b->buf = (uint8_t *)buf;
398
+ b->buf_len = buf_len;
399
+ b->generation = generation;
400
+ b->item_count = 0;
401
+ b->max_items = max_items;
402
+ b->error = NIPC_OK;
403
+ if (mul_would_overflow((size_t)max_items, NIPC_LOOKUP_DIR_ENTRY_SIZE)) {
404
+ b->data_offset = SIZE_MAX;
405
+ } else {
406
+ size_t dir_size = (size_t)max_items * NIPC_LOOKUP_DIR_ENTRY_SIZE;
407
+#if SIZE_MAX <= UINT32_MAX
408
+ if (dir_size > SIZE_MAX - NIPC_APPS_LOOKUP_RESP_HDR_SIZE) {
409
+ b->data_offset = SIZE_MAX;
410
+ } else
411
+#endif
412
+ {
413
+ b->data_offset = NIPC_APPS_LOOKUP_RESP_HDR_SIZE + dir_size;
414
+ }
415
+ }
416
+}
417
+
418
+void nipc_apps_lookup_builder_set_generation(nipc_apps_lookup_builder_t *b,
419
+ uint64_t generation) {
420
+ b->generation = generation;
421
+}
422
+
423
+uint32_t nipc_apps_lookup_builder_estimate_max_items(size_t buf_len) {
424
+ if (buf_len <= NIPC_APPS_LOOKUP_RESP_HDR_SIZE)
425
+ return 0;
426
+ size_t min_item = nipc_align8(NIPC_APPS_LOOKUP_ITEM_HDR_SIZE + 3u);
427
+ return (uint32_t)((buf_len - NIPC_APPS_LOOKUP_RESP_HDR_SIZE) /
428
+ (NIPC_LOOKUP_DIR_ENTRY_SIZE + min_item));
429
+}
430
+
431
+nipc_error_t nipc_apps_lookup_builder_add(
432
+ nipc_apps_lookup_builder_t *b, uint16_t status, uint16_t cgroup_status,
433
+ uint16_t orchestrator, uint32_t pid, uint32_t ppid, uint32_t uid,
434
+ uint64_t starttime, const char *comm, uint32_t comm_len,
435
+ const char *cgroup_path, uint32_t cgroup_path_len, const char *cgroup_name,
436
+ uint32_t cgroup_name_len, const nipc_lookup_label_view_t *labels,
437
+ uint16_t label_count) {
438
+ if (b->item_count >= b->max_items) {
439
+ b->error = NIPC_ERR_OVERFLOW;
440
+ return b->error;
441
+ }
442
+ b->error = apps_lookup_validate_semantics(
443
+ status, cgroup_status, orchestrator, ppid, uid, starttime, comm_len,
444
+ cgroup_path_len, cgroup_name_len, label_count);
445
+ if (b->error != NIPC_OK)
446
+ return b->error;
447
+
448
+ nipc_lookup_builder_string_t strings[] = {
449
+ {
450
+ .ptr = comm,
451
+ .len = comm_len,
452
+ .require_non_empty = status == NIPC_PID_LOOKUP_KNOWN,
453
+ },
454
+ {.ptr = cgroup_path, .len = cgroup_path_len, .require_non_empty = false},
455
+ {.ptr = cgroup_name, .len = cgroup_name_len, .require_non_empty = false},
456
+ };
457
+ b->error = nipc_lookup_builder_validate_strings(strings, 3);
458
+ if (b->error != NIPC_OK)
459
+ return b->error;
460
+
461
+ nipc_lookup_builder_item_layout_t layout = {0};
462
+ b->error = nipc_lookup_builder_layout_item(
463
+ b->data_offset, b->buf_len, NIPC_APPS_LOOKUP_ITEM_HDR_SIZE, strings, 3,
464
+ labels, label_count, &layout);
465
+ if (b->error != NIPC_OK)
466
+ return b->error;
467
+
468
+ size_t item_start = (size_t)layout.item_start;
469
+ size_t item_size = (size_t)layout.item_size;
470
+
471
+ if (item_start > b->data_offset)
472
+ memset(b->buf + b->data_offset, 0, item_start - b->data_offset);
473
+
474
+ uint8_t *item = b->buf + item_start;
475
+ nipc_apps_lookup_item_wire_t wire = {
476
+ .layout_version = 1,
477
+ .status = status,
478
+ .orchestrator = orchestrator,
479
+ .cgroup_status = cgroup_status,
480
+ .pid = pid,
481
+ .ppid = ppid,
482
+ .uid = uid,
483
+ .reserved0 = 0,
484
+ .starttime = starttime,
485
+ .comm_offset = (uint32_t)strings[0].offset,
486
+ .comm_length = comm_len,
487
+ .cgroup_path_offset = (uint32_t)strings[1].offset,
488
+ .cgroup_path_length = cgroup_path_len,
489
+ .cgroup_name_offset = (uint32_t)strings[2].offset,
490
+ .cgroup_name_length = cgroup_name_len,
491
+ .label_count = label_count,
492
+ .reserved1 = 0,
493
+ };
494
+ memcpy(item, &wire, NIPC_APPS_LOOKUP_ITEM_HDR_SIZE);
495
+ nipc_lookup_builder_write_strings(item, strings, 3);
496
+
497
+ if (label_count > 0) {
498
+ size_t fixed_end = (size_t)layout.fixed_end;
499
+ size_t table_start = (size_t)layout.table_start;
500
+ if (table_start > fixed_end)
501
+ memset(item + fixed_end, 0, table_start - fixed_end);
502
+ nipc_lookup_write_labels(item, table_start, (size_t)layout.table_bytes,
503
+ labels, label_count);
504
+ }
505
+
506
+ nipc_lookup_builder_write_dir_entry(b->buf, NIPC_APPS_LOOKUP_RESP_HDR_SIZE,
507
+ b->item_count, item_start, item_size);
508
+
509
+ b->data_offset = item_start + item_size;
510
+ b->item_count++;
511
+ return NIPC_OK;
512
+}
513
+
514
+size_t nipc_apps_lookup_builder_finish(nipc_apps_lookup_builder_t *b) {
515
+ return nipc_lookup_finish_common(
516
+ b->buf, b->buf_len, b->item_count, b->data_offset,
517
+ NIPC_APPS_LOOKUP_RESP_HDR_SIZE, b->generation);
518
+}
519
+
520
+nipc_error_t nipc_dispatch_apps_lookup(
521
+ const uint8_t *req, size_t req_len,
522
+ uint8_t *resp, size_t resp_size, size_t *resp_len,
523
+ nipc_apps_lookup_handler_fn handler, void *user)
524
+{
525
+ nipc_apps_lookup_req_view_t request;
526
+ nipc_error_t err = nipc_apps_lookup_req_decode(req, req_len, &request);
527
+ if (err != NIPC_OK)
528
+ return err;
529
+
530
+ nipc_apps_lookup_builder_t builder;
531
+ nipc_apps_lookup_builder_init(&builder, resp, resp_size,
532
+ request.item_count, 0);
533
+
534
+ if (!handler(user, &request, &builder)) {
535
+ if (builder.error != NIPC_OK)
536
+ return builder.error;
537
+ return NIPC_ERR_HANDLER_FAILED;
538
+ }
539
+
540
+ if (builder.error != NIPC_OK)
541
+ return builder.error;
542
+ if (builder.item_count != request.item_count)
543
+ return NIPC_ERR_BAD_ITEM_COUNT;
544
+
545
+ *resp_len = nipc_apps_lookup_builder_finish(&builder);
546
+ return (*resp_len > 0) ? NIPC_OK : NIPC_ERR_OVERFLOW;
547
+}
src/libnetdata/netipc/src/protocol/netipc_protocol_apps_lookup_internal.h
new
+31
@@ -0,0 +1,31 @@
1
+#ifndef NETIPC_PROTOCOL_APPS_LOOKUP_INTERNAL_H
2
+#define NETIPC_PROTOCOL_APPS_LOOKUP_INTERNAL_H
3
+
4
+#include "netipc_protocol_lookup_common.h"
5
+
6
+typedef struct {
7
+ uint32_t pid;
8
+ uint32_t reserved;
9
+} nipc_apps_lookup_key_wire_t;
10
+
11
+typedef struct {
12
+ uint16_t layout_version;
13
+ uint16_t status;
14
+ uint16_t orchestrator;
15
+ uint16_t cgroup_status;
16
+ uint32_t pid;
17
+ uint32_t ppid;
18
+ uint32_t uid;
19
+ uint32_t reserved0;
20
+ uint64_t starttime;
21
+ uint32_t comm_offset;
22
+ uint32_t comm_length;
23
+ uint32_t cgroup_path_offset;
24
+ uint32_t cgroup_path_length;
25
+ uint32_t cgroup_name_offset;
26
+ uint32_t cgroup_name_length;
27
+ uint16_t label_count;
28
+ uint16_t reserved1;
29
+} nipc_apps_lookup_item_wire_t;
30
+
31
+#endif /* NETIPC_PROTOCOL_APPS_LOOKUP_INTERNAL_H */
src/libnetdata/netipc/src/protocol/netipc_protocol_cgroups_lookup.c
new
+450
@@ -0,0 +1,450 @@
1
+#include "netipc_protocol_cgroups_lookup_internal.h"
2
+
3
+_Static_assert(sizeof(nipc_cgroups_lookup_item_wire_t) == NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE,
4
+ "cgroups lookup item header must be 28 bytes");
5
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, layout_version) == 0, "");
6
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, status) == 2, "");
7
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, orchestrator) == 4, "");
8
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, reserved0) == 6, "");
9
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, path_offset) == 8, "");
10
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, path_length) == 12, "");
11
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, name_offset) == 16, "");
12
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, name_length) == 20, "");
13
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, label_count) == 24, "");
14
+_Static_assert(offsetof(nipc_cgroups_lookup_item_wire_t, reserved1) == 26, "");
15
+
16
+static nipc_error_t cgroups_lookup_validate_semantics(uint16_t status,
17
+ uint16_t orchestrator,
18
+ uint64_t path_len,
19
+ uint64_t name_len,
20
+ uint64_t label_count) {
21
+ if (status != NIPC_CGROUP_LOOKUP_KNOWN &&
22
+ status != NIPC_CGROUP_LOOKUP_UNKNOWN_RETRY_LATER &&
23
+ status != NIPC_CGROUP_LOOKUP_UNKNOWN_PERMANENT)
24
+ return NIPC_ERR_BAD_LAYOUT;
25
+ if (path_len == 0)
26
+ return NIPC_ERR_BAD_LAYOUT;
27
+ if (status != NIPC_CGROUP_LOOKUP_KNOWN &&
28
+ (orchestrator != 0 || name_len != 0 || label_count != 0))
29
+ return NIPC_ERR_BAD_LAYOUT;
30
+ return NIPC_OK;
31
+}
32
+
33
+/* ------------------------------------------------------------------ */
34
+/* Cgroups lookup request */
35
+/* ------------------------------------------------------------------ */
36
+
37
+size_t nipc_cgroups_lookup_req_encode(const nipc_str_view_t *paths,
38
+ uint32_t item_count, void *buf,
39
+ size_t buf_len) {
40
+ if (mul_would_overflow((size_t)item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
41
+ return 0;
42
+
43
+ size_t dir_size = (size_t)item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
44
+#if SIZE_MAX <= UINT32_MAX
45
+ if (dir_size > SIZE_MAX - NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE)
46
+ return 0;
47
+#endif
48
+ size_t packed_start = NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE + dir_size;
49
+ if (buf_len < packed_start)
50
+ return 0;
51
+
52
+ uint8_t *p = (uint8_t *)buf;
53
+ size_t data = packed_start;
54
+
55
+ for (uint32_t i = 0; i < item_count; i++) {
56
+ if (!paths ||
57
+ nipc_lookup_source_string_invalid(paths[i].ptr, paths[i].len, true))
58
+ return 0;
59
+
60
+ size_t aligned = nipc_align8(data);
61
+ uint64_t key_len_u64;
62
+ if (nipc_lookup_add_u64_over_limit(paths[i].len, 1u, UINT32_MAX,
63
+ &key_len_u64))
64
+ return 0;
65
+ size_t key_len = (size_t)key_len_u64;
66
+ if (aligned < data || key_len > SIZE_MAX - aligned ||
67
+ aligned + key_len > buf_len)
68
+ return 0;
69
+ size_t key_offset = aligned - packed_start;
70
+ if (key_offset > UINT32_MAX || key_len > UINT32_MAX)
71
+ return 0;
72
+ if (aligned > data)
73
+ memset(p + data, 0, aligned - data);
74
+
75
+ nipc_lookup_dir_entry_t entry = {
76
+ .offset = (uint32_t)key_offset,
77
+ .length = (uint32_t)key_len,
78
+ };
79
+ memcpy(p + NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE +
80
+ (size_t)i * NIPC_LOOKUP_DIR_ENTRY_SIZE,
81
+ &entry, sizeof(entry));
82
+ memcpy(p + aligned, paths[i].ptr, paths[i].len);
83
+ p[aligned + paths[i].len] = '\0';
84
+ data = aligned + key_len;
85
+ }
86
+
87
+ nipc_lookup_req_header_wire_t hdr = {
88
+ .layout_version = 1,
89
+ .flags = 0,
90
+ .item_count = item_count,
91
+ .reserved0 = 0,
92
+ .reserved1 = 0,
93
+ };
94
+ memcpy(p, &hdr, sizeof(hdr));
95
+ return data;
96
+}
97
+
98
+nipc_error_t
99
+nipc_cgroups_lookup_req_decode(const void *buf, size_t buf_len,
100
+ nipc_cgroups_lookup_req_view_t *out) {
101
+ if (buf_len < NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE)
102
+ return NIPC_ERR_TRUNCATED;
103
+
104
+ nipc_lookup_req_header_wire_t hdr;
105
+ memcpy(&hdr, buf, sizeof(hdr));
106
+ if (hdr.layout_version != 1 || hdr.flags != 0 || hdr.reserved0 != 0 ||
107
+ hdr.reserved1 != 0)
108
+ return NIPC_ERR_BAD_LAYOUT;
109
+
110
+ if (mul_would_overflow((size_t)hdr.item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
111
+ return NIPC_ERR_BAD_ITEM_COUNT;
112
+ size_t dir_size = (size_t)hdr.item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
113
+ size_t dir_end = NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE + dir_size;
114
+ if (dir_end > buf_len)
115
+ return NIPC_ERR_TRUNCATED;
116
+ size_t packed_area_len = buf_len - dir_end;
117
+ if (packed_area_len > UINT32_MAX)
118
+ return NIPC_ERR_BAD_ITEM_COUNT;
119
+
120
+ const uint8_t *p = (const uint8_t *)buf;
121
+ const uint8_t *dir = p + NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE;
122
+ nipc_error_t err = nipc_lookup_validate_ordered_dir(
123
+ dir, hdr.item_count, (uint32_t)packed_area_len, 2, false, 0);
124
+ if (err != NIPC_OK)
125
+ return err;
126
+
127
+ const uint8_t *packed = p + dir_end;
128
+ for (uint32_t i = 0; i < hdr.item_count; i++) {
129
+ nipc_lookup_dir_entry_t entry;
130
+ memcpy(&entry, dir + (size_t)i * NIPC_LOOKUP_DIR_ENTRY_SIZE, sizeof(entry));
131
+ const uint8_t *key = packed + entry.offset;
132
+ if (key[entry.length - 1] != '\0')
133
+ return NIPC_ERR_MISSING_NUL;
134
+ if (nipc_lookup_bytes_have_nul(key, entry.length - 1))
135
+ return NIPC_ERR_BAD_LAYOUT;
136
+ }
137
+
138
+ out->item_count = hdr.item_count;
139
+ out->_payload = p;
140
+ out->_payload_len = buf_len;
141
+ return NIPC_OK;
142
+}
143
+
144
+nipc_error_t
145
+nipc_cgroups_lookup_req_item(const nipc_cgroups_lookup_req_view_t *view,
146
+ uint32_t index,
147
+ nipc_cgroups_lookup_req_item_t *out) {
148
+ if (index >= view->item_count)
149
+ return NIPC_ERR_OUT_OF_BOUNDS;
150
+
151
+ /* Decode validates this, but item accessors are public and may be
152
+ * called with manually constructed views. */
153
+ if (mul_would_overflow((size_t)view->item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
154
+ return NIPC_ERR_BAD_ITEM_COUNT;
155
+
156
+ size_t dir_size = (size_t)view->item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
157
+ size_t dir_end = NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE + dir_size;
158
+ const uint8_t *dir = view->_payload + NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE;
159
+ const uint8_t *packed = view->_payload + dir_end;
160
+
161
+ nipc_lookup_dir_entry_t entry;
162
+ memcpy(&entry, dir + (size_t)index * NIPC_LOOKUP_DIR_ENTRY_SIZE,
163
+ sizeof(entry));
164
+ out->path.ptr = (const char *)(packed + entry.offset);
165
+ out->path.len = entry.length - 1;
166
+ return NIPC_OK;
167
+}
168
+
169
+/* ------------------------------------------------------------------ */
170
+/* Cgroups lookup response */
171
+/* ------------------------------------------------------------------ */
172
+
173
+static nipc_error_t
174
+cgroups_lookup_decode_item_bytes(const uint8_t *item, uint32_t item_len,
175
+ nipc_cgroups_lookup_item_view_t *out) {
176
+ if (item_len < NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE)
177
+ return NIPC_ERR_TRUNCATED;
178
+
179
+ nipc_cgroups_lookup_item_wire_t wire;
180
+ memcpy(&wire, item, NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE);
181
+
182
+ if (wire.layout_version != 1 || wire.reserved0 != 0 || wire.reserved1 != 0)
183
+ return NIPC_ERR_BAD_LAYOUT;
184
+
185
+ nipc_error_t err = cgroups_lookup_validate_semantics(
186
+ wire.status, wire.orchestrator, wire.path_length, wire.name_length,
187
+ wire.label_count);
188
+ if (err != NIPC_OK)
189
+ return err;
190
+
191
+ nipc_str_view_t path, name;
192
+ uint64_t path_end, name_end;
193
+ err = nipc_lookup_string_view(
194
+ item, item_len, NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE, wire.path_offset,
195
+ wire.path_length, &path, &path_end);
196
+ if (err != NIPC_OK)
197
+ return err;
198
+ err = nipc_lookup_string_view(
199
+ item, item_len, NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE, wire.name_offset,
200
+ wire.name_length, &name, &name_end);
201
+ if (err != NIPC_OK)
202
+ return err;
203
+ if (nipc_lookup_ranges_overlap_u64(wire.path_offset, path_end,
204
+ wire.name_offset, name_end))
205
+ return NIPC_ERR_BAD_LAYOUT;
206
+
207
+ uint64_t fixed_end = path_end > name_end ? path_end : name_end;
208
+ uint32_t label_table_offset = 0;
209
+ err = nipc_lookup_validate_labels(
210
+ item, item_len, NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE, wire.label_count,
211
+ fixed_end, &label_table_offset);
212
+ if (err != NIPC_OK)
213
+ return err;
214
+
215
+ if (out) {
216
+ out->status = wire.status;
217
+ out->orchestrator = wire.orchestrator;
218
+ out->path = path;
219
+ out->name = name;
220
+ out->label_count = wire.label_count;
221
+ out->_item = item;
222
+ out->_item_len = item_len;
223
+ out->_label_table_offset = label_table_offset;
224
+ }
225
+ return NIPC_OK;
226
+}
227
+
228
+nipc_error_t
229
+nipc_cgroups_lookup_resp_decode(const void *buf, size_t buf_len,
230
+ nipc_cgroups_lookup_resp_view_t *out) {
231
+ if (buf_len < NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE)
232
+ return NIPC_ERR_TRUNCATED;
233
+
234
+ nipc_lookup_resp_header_wire_t hdr;
235
+ memcpy(&hdr, buf, sizeof(hdr));
236
+ if (hdr.layout_version != 1 || hdr.flags != 0)
237
+ return NIPC_ERR_BAD_LAYOUT;
238
+
239
+ if (mul_would_overflow((size_t)hdr.item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
240
+ return NIPC_ERR_BAD_ITEM_COUNT;
241
+ size_t dir_size = (size_t)hdr.item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
242
+ size_t dir_end = NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE + dir_size;
243
+ if (dir_end > buf_len)
244
+ return NIPC_ERR_TRUNCATED;
245
+ size_t packed_area_len = buf_len - dir_end;
246
+ if (packed_area_len > UINT32_MAX)
247
+ return NIPC_ERR_BAD_ITEM_COUNT;
248
+
249
+ const uint8_t *p = (const uint8_t *)buf;
250
+ const uint8_t *dir = p + NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE;
251
+ nipc_error_t err = nipc_lookup_validate_ordered_dir(
252
+ dir, hdr.item_count, (uint32_t)packed_area_len,
253
+ NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE, false, 0);
254
+ if (err != NIPC_OK)
255
+ return err;
256
+
257
+ const uint8_t *packed = p + dir_end;
258
+ for (uint32_t i = 0; i < hdr.item_count; i++) {
259
+ nipc_lookup_dir_entry_t entry;
260
+ memcpy(&entry, dir + (size_t)i * NIPC_LOOKUP_DIR_ENTRY_SIZE, sizeof(entry));
261
+ err = cgroups_lookup_decode_item_bytes(packed + entry.offset, entry.length,
262
+ NULL);
263
+ if (err != NIPC_OK)
264
+ return err;
265
+ }
266
+
267
+ out->layout_version = hdr.layout_version;
268
+ out->flags = hdr.flags;
269
+ out->item_count = hdr.item_count;
270
+ out->generation = hdr.generation;
271
+ out->_payload = p;
272
+ out->_payload_len = buf_len;
273
+ return NIPC_OK;
274
+}
275
+
276
+nipc_error_t
277
+nipc_cgroups_lookup_resp_item(const nipc_cgroups_lookup_resp_view_t *view,
278
+ uint32_t index,
279
+ nipc_cgroups_lookup_item_view_t *out) {
280
+ if (index >= view->item_count)
281
+ return NIPC_ERR_OUT_OF_BOUNDS;
282
+
283
+ /* Decode validates this, but item accessors are public and may be
284
+ * called with manually constructed views. */
285
+ if (mul_would_overflow((size_t)view->item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
286
+ return NIPC_ERR_BAD_ITEM_COUNT;
287
+
288
+ size_t dir_size = (size_t)view->item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
289
+ size_t dir_end = NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE + dir_size;
290
+ const uint8_t *dir = view->_payload + NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE;
291
+ const uint8_t *packed = view->_payload + dir_end;
292
+ nipc_lookup_dir_entry_t entry;
293
+ memcpy(&entry, dir + (size_t)index * NIPC_LOOKUP_DIR_ENTRY_SIZE,
294
+ sizeof(entry));
295
+ return cgroups_lookup_decode_item_bytes(packed + entry.offset, entry.length,
296
+ out);
297
+}
298
+
299
+nipc_error_t
300
+nipc_cgroups_lookup_item_label(const nipc_cgroups_lookup_item_view_t *item,
301
+ uint32_t index, nipc_lookup_label_view_t *out) {
302
+ return nipc_lookup_label_at(
303
+ item->_item, item->_item_len, NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE,
304
+ item->label_count, item->_label_table_offset, index, out);
305
+}
306
+
307
+void nipc_cgroups_lookup_builder_init(nipc_cgroups_lookup_builder_t *b,
308
+ void *buf, size_t buf_len,
309
+ uint32_t max_items, uint64_t generation) {
310
+ b->buf = (uint8_t *)buf;
311
+ b->buf_len = buf_len;
312
+ b->generation = generation;
313
+ b->item_count = 0;
314
+ b->max_items = max_items;
315
+ b->error = NIPC_OK;
316
+ if (mul_would_overflow((size_t)max_items, NIPC_LOOKUP_DIR_ENTRY_SIZE)) {
317
+ b->data_offset = SIZE_MAX;
318
+ } else {
319
+ size_t dir_size = (size_t)max_items * NIPC_LOOKUP_DIR_ENTRY_SIZE;
320
+#if SIZE_MAX <= UINT32_MAX
321
+ if (dir_size > SIZE_MAX - NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE) {
322
+ b->data_offset = SIZE_MAX;
323
+ } else
324
+#endif
325
+ {
326
+ b->data_offset = NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE + dir_size;
327
+ }
328
+ }
329
+}
330
+
331
+void nipc_cgroups_lookup_builder_set_generation(
332
+ nipc_cgroups_lookup_builder_t *b, uint64_t generation) {
333
+ b->generation = generation;
334
+}
335
+
336
+uint32_t nipc_cgroups_lookup_builder_estimate_max_items(size_t buf_len) {
337
+ if (buf_len <= NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE)
338
+ return 0;
339
+ size_t min_item = nipc_align8(NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE + 2u + 1u);
340
+ return (uint32_t)((buf_len - NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE) /
341
+ (NIPC_LOOKUP_DIR_ENTRY_SIZE + min_item));
342
+}
343
+
344
+nipc_error_t nipc_cgroups_lookup_builder_add(
345
+ nipc_cgroups_lookup_builder_t *b, uint16_t status, uint16_t orchestrator,
346
+ const char *path, uint32_t path_len, const char *name, uint32_t name_len,
347
+ const nipc_lookup_label_view_t *labels, uint16_t label_count) {
348
+ if (b->item_count >= b->max_items) {
349
+ b->error = NIPC_ERR_OVERFLOW;
350
+ return b->error;
351
+ }
352
+ nipc_error_t err = cgroups_lookup_validate_semantics(
353
+ status, orchestrator, path_len, name_len, label_count);
354
+ if (err != NIPC_OK) {
355
+ b->error = err;
356
+ return b->error;
357
+ }
358
+
359
+ nipc_lookup_builder_string_t strings[] = {
360
+ {.ptr = path, .len = path_len, .require_non_empty = true},
361
+ {.ptr = name, .len = name_len, .require_non_empty = false},
362
+ };
363
+ err = nipc_lookup_builder_validate_strings(strings, 2);
364
+ if (err != NIPC_OK) {
365
+ b->error = err;
366
+ return b->error;
367
+ }
368
+
369
+ nipc_lookup_builder_item_layout_t layout = {0};
370
+ err = nipc_lookup_builder_layout_item(
371
+ b->data_offset, b->buf_len, NIPC_CGROUPS_LOOKUP_ITEM_HDR_SIZE, strings, 2,
372
+ labels, label_count, &layout);
373
+ if (err != NIPC_OK) {
374
+ b->error = err;
375
+ return b->error;
376
+ }
377
+
378
+ size_t item_start = (size_t)layout.item_start;
379
+ size_t item_size = (size_t)layout.item_size;
380
+
381
+ if (item_start > b->data_offset)
382
+ memset(b->buf + b->data_offset, 0, item_start - b->data_offset);
383
+
384
+ uint8_t *item = b->buf + item_start;
385
+ nipc_cgroups_lookup_item_wire_t wire = {
386
+ .layout_version = 1,
387
+ .status = status,
388
+ .orchestrator = orchestrator,
389
+ .reserved0 = 0,
390
+ .path_offset = (uint32_t)strings[0].offset,
391
+ .path_length = path_len,
392
+ .name_offset = (uint32_t)strings[1].offset,
393
+ .name_length = name_len,
394
+ .label_count = label_count,
395
+ .reserved1 = 0,
396
+ };
397
+ memcpy(item, &wire, sizeof(wire));
398
+ nipc_lookup_builder_write_strings(item, strings, 2);
399
+
400
+ if (label_count > 0) {
401
+ size_t fixed_end = (size_t)layout.fixed_end;
402
+ size_t table_start = (size_t)layout.table_start;
403
+ if (table_start > fixed_end)
404
+ memset(item + fixed_end, 0, table_start - fixed_end);
405
+ nipc_lookup_write_labels(item, table_start, (size_t)layout.table_bytes,
406
+ labels, label_count);
407
+ }
408
+
409
+ nipc_lookup_builder_write_dir_entry(b->buf, NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE,
410
+ b->item_count, item_start, item_size);
411
+
412
+ b->data_offset = item_start + item_size;
413
+ b->item_count++;
414
+ return NIPC_OK;
415
+}
416
+
417
+size_t nipc_cgroups_lookup_builder_finish(nipc_cgroups_lookup_builder_t *b) {
418
+ return nipc_lookup_finish_common(
419
+ b->buf, b->buf_len, b->item_count, b->data_offset,
420
+ NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE, b->generation);
421
+}
422
+
423
+nipc_error_t nipc_dispatch_cgroups_lookup(
424
+ const uint8_t *req, size_t req_len,
425
+ uint8_t *resp, size_t resp_size, size_t *resp_len,
426
+ nipc_cgroups_lookup_handler_fn handler, void *user)
427
+{
428
+ nipc_cgroups_lookup_req_view_t request;
429
+ nipc_error_t err = nipc_cgroups_lookup_req_decode(req, req_len, &request);
430
+ if (err != NIPC_OK)
431
+ return err;
432
+
433
+ nipc_cgroups_lookup_builder_t builder;
434
+ nipc_cgroups_lookup_builder_init(&builder, resp, resp_size,
435
+ request.item_count, 0);
436
+
437
+ if (!handler(user, &request, &builder)) {
438
+ if (builder.error != NIPC_OK)
439
+ return builder.error;
440
+ return NIPC_ERR_HANDLER_FAILED;
441
+ }
442
+
443
+ if (builder.error != NIPC_OK)
444
+ return builder.error;
445
+ if (builder.item_count != request.item_count)
446
+ return NIPC_ERR_BAD_ITEM_COUNT;
447
+
448
+ *resp_len = nipc_cgroups_lookup_builder_finish(&builder);
449
+ return (*resp_len > 0) ? NIPC_OK : NIPC_ERR_OVERFLOW;
450
+}
src/libnetdata/netipc/src/protocol/netipc_protocol_cgroups_lookup_internal.h
new
+19
@@ -0,0 +1,19 @@
1
+#ifndef NETIPC_PROTOCOL_CGROUPS_LOOKUP_INTERNAL_H
2
+#define NETIPC_PROTOCOL_CGROUPS_LOOKUP_INTERNAL_H
3
+
4
+#include "netipc_protocol_lookup_common.h"
5
+
6
+typedef struct {
7
+ uint16_t layout_version;
8
+ uint16_t status;
9
+ uint16_t orchestrator;
10
+ uint16_t reserved0;
11
+ uint32_t path_offset;
12
+ uint32_t path_length;
13
+ uint32_t name_offset;
14
+ uint32_t name_length;
15
+ uint16_t label_count;
16
+ uint16_t reserved1;
17
+} nipc_cgroups_lookup_item_wire_t;
18
+
19
+#endif /* NETIPC_PROTOCOL_CGROUPS_LOOKUP_INTERNAL_H */
src/libnetdata/netipc/src/protocol/netipc_protocol_cgroups_snapshot.c
new
+367
@@ -0,0 +1,367 @@
1
+#include "netipc_protocol_cgroups_snapshot_internal.h"
2
+
3
+/* Cgroups request (4 bytes) */
4
+_Static_assert(sizeof(nipc_cgroups_req_t) == 4,
5
+ "nipc_cgroups_req_t must be 4 bytes");
6
+
7
+/* Cgroups snapshot response header (24 bytes) */
8
+_Static_assert(sizeof(nipc_cgroups_resp_header_t) == 24,
9
+ "nipc_cgroups_resp_header_t must be 24 bytes");
10
+_Static_assert(offsetof(nipc_cgroups_resp_header_t, layout_version) == 0, "");
11
+_Static_assert(offsetof(nipc_cgroups_resp_header_t, flags) == 2, "");
12
+_Static_assert(offsetof(nipc_cgroups_resp_header_t, item_count) == 4, "");
13
+_Static_assert(offsetof(nipc_cgroups_resp_header_t, systemd_enabled) == 8, "");
14
+_Static_assert(offsetof(nipc_cgroups_resp_header_t, reserved) == 12, "");
15
+_Static_assert(offsetof(nipc_cgroups_resp_header_t, generation) == 16, "");
16
+
17
+_Static_assert(sizeof(nipc_cgroups_item_wire_t) == 32,
18
+ "nipc_cgroups_item_wire_t must be 32 bytes");
19
+
20
+/* ------------------------------------------------------------------ */
21
+/* Cgroups snapshot request (4 bytes) */
22
+/* ------------------------------------------------------------------ */
23
+
24
+size_t nipc_cgroups_req_encode(const nipc_cgroups_req_t *r,
25
+ void *buf, size_t buf_len) {
26
+ if (buf_len < sizeof(nipc_cgroups_req_t))
27
+ return 0;
28
+
29
+ memcpy(buf, r, sizeof(nipc_cgroups_req_t));
30
+ return sizeof(nipc_cgroups_req_t);
31
+}
32
+
33
+nipc_error_t nipc_cgroups_req_decode(const void *buf, size_t buf_len,
34
+ nipc_cgroups_req_t *out) {
35
+ if (buf_len < sizeof(nipc_cgroups_req_t))
36
+ return NIPC_ERR_TRUNCATED;
37
+
38
+ memcpy(out, buf, sizeof(nipc_cgroups_req_t));
39
+
40
+ if (out->layout_version != 1)
41
+ return NIPC_ERR_BAD_LAYOUT;
42
+ if (out->flags != 0)
43
+ return NIPC_ERR_BAD_LAYOUT;
44
+
45
+ return NIPC_OK;
46
+}
47
+
48
+/* ------------------------------------------------------------------ */
49
+/* Cgroups snapshot response decode */
50
+/* ------------------------------------------------------------------ */
51
+
52
+nipc_error_t nipc_cgroups_resp_decode(const void *buf, size_t buf_len,
53
+ nipc_cgroups_resp_view_t *out) {
54
+ if (buf_len < NIPC_CGROUPS_RESP_HDR_SIZE)
55
+ return NIPC_ERR_TRUNCATED;
56
+
57
+ nipc_cgroups_resp_header_t hdr;
58
+ memcpy(&hdr, buf, sizeof(hdr));
59
+
60
+ if (hdr.layout_version != 1)
61
+ return NIPC_ERR_BAD_LAYOUT;
62
+ if (hdr.flags != 0)
63
+ return NIPC_ERR_BAD_LAYOUT;
64
+ if (hdr.reserved != 0)
65
+ return NIPC_ERR_BAD_LAYOUT;
66
+
67
+ out->layout_version = hdr.layout_version;
68
+ out->flags = hdr.flags;
69
+ out->item_count = hdr.item_count;
70
+ out->systemd_enabled = hdr.systemd_enabled;
71
+ out->generation = hdr.generation;
72
+
73
+ /* Validate directory fits (with overflow check) */
74
+ if (mul_would_overflow((size_t)out->item_count, NIPC_CGROUPS_DIR_ENTRY_SIZE))
75
+ return NIPC_ERR_BAD_ITEM_COUNT;
76
+ size_t dir_size = (size_t)out->item_count * NIPC_CGROUPS_DIR_ENTRY_SIZE;
77
+ size_t dir_end = NIPC_CGROUPS_RESP_HDR_SIZE + dir_size;
78
+ if (dir_end > buf_len)
79
+ return NIPC_ERR_TRUNCATED;
80
+
81
+ size_t packed_area_len = buf_len - dir_end;
82
+
83
+ /* Validate each directory entry */
84
+ const uint8_t *dir = (const uint8_t *)buf + NIPC_CGROUPS_RESP_HDR_SIZE;
85
+ for (uint32_t i = 0; i < out->item_count; i++) {
86
+ nipc_batch_entry_t entry;
87
+ memcpy(&entry, dir + i * sizeof(entry), sizeof(entry));
88
+
89
+ if (entry.offset % NIPC_ALIGNMENT != 0)
90
+ return NIPC_ERR_BAD_ALIGNMENT;
91
+ if ((uint64_t)entry.offset + entry.length > packed_area_len)
92
+ return NIPC_ERR_OUT_OF_BOUNDS;
93
+ if (entry.length < NIPC_CGROUPS_ITEM_HDR_SIZE)
94
+ return NIPC_ERR_TRUNCATED;
95
+ }
96
+
97
+ out->_payload = (const uint8_t *)buf;
98
+ out->_payload_len = buf_len;
99
+ return NIPC_OK;
100
+}
101
+
102
+nipc_error_t nipc_cgroups_resp_item(const nipc_cgroups_resp_view_t *view,
103
+ uint32_t index,
104
+ nipc_cgroups_item_view_t *out) {
105
+ if (index >= view->item_count)
106
+ return NIPC_ERR_OUT_OF_BOUNDS;
107
+
108
+ /* Overflow already checked in nipc_cgroups_resp_decode, but
109
+ * guard defensively since this is a public API. */
110
+ if (mul_would_overflow((size_t)view->item_count, NIPC_CGROUPS_DIR_ENTRY_SIZE))
111
+ return NIPC_ERR_BAD_ITEM_COUNT;
112
+
113
+ size_t dir_start = NIPC_CGROUPS_RESP_HDR_SIZE;
114
+ size_t dir_size = (size_t)view->item_count * NIPC_CGROUPS_DIR_ENTRY_SIZE;
115
+ size_t packed_area_start = dir_start + dir_size;
116
+
117
+ /* Read directory entry */
118
+ nipc_batch_entry_t dir_entry;
119
+ memcpy(&dir_entry,
120
+ view->_payload + dir_start + index * sizeof(dir_entry),
121
+ sizeof(dir_entry));
122
+
123
+ const uint8_t *item = view->_payload + packed_area_start + dir_entry.offset;
124
+ uint32_t item_len = dir_entry.length;
125
+
126
+ /* Read the 32-byte item wire header in one copy */
127
+ nipc_cgroups_item_wire_t wire;
128
+ memcpy(&wire, item, sizeof(wire));
129
+
130
+ if (wire.layout_version != 1)
131
+ return NIPC_ERR_BAD_LAYOUT;
132
+ if (wire.flags != 0)
133
+ return NIPC_ERR_BAD_LAYOUT;
134
+
135
+ /* Validate name string */
136
+ if (wire.name_offset < NIPC_CGROUPS_ITEM_HDR_SIZE)
137
+ return NIPC_ERR_OUT_OF_BOUNDS;
138
+ if ((uint64_t)wire.name_offset + wire.name_length + 1 > item_len)
139
+ return NIPC_ERR_OUT_OF_BOUNDS;
140
+ if (item[wire.name_offset + wire.name_length] != '\0')
141
+ return NIPC_ERR_MISSING_NUL;
142
+
143
+ /* Validate path string */
144
+ if (wire.path_offset < NIPC_CGROUPS_ITEM_HDR_SIZE)
145
+ return NIPC_ERR_OUT_OF_BOUNDS;
146
+ if ((uint64_t)wire.path_offset + wire.path_length + 1 > item_len)
147
+ return NIPC_ERR_OUT_OF_BOUNDS;
148
+ if (item[wire.path_offset + wire.path_length] != '\0')
149
+ return NIPC_ERR_MISSING_NUL;
150
+
151
+ /* Reject overlapping name and path regions (including NUL) */
152
+ {
153
+ uint64_t name_start = wire.name_offset;
154
+ uint64_t name_end = name_start + wire.name_length + 1;
155
+ uint64_t path_start = wire.path_offset;
156
+ uint64_t path_end = path_start + wire.path_length + 1;
157
+ if (name_start < path_end && path_start < name_end)
158
+ return NIPC_ERR_BAD_LAYOUT;
159
+ }
160
+
161
+ out->layout_version = wire.layout_version;
162
+ out->flags = wire.flags;
163
+ out->hash = wire.hash;
164
+ out->options = wire.options;
165
+ out->enabled = wire.enabled;
166
+ out->name.ptr = (const char *)(item + wire.name_offset);
167
+ out->name.len = wire.name_length;
168
+ out->path.ptr = (const char *)(item + wire.path_offset);
169
+ out->path.len = wire.path_length;
170
+
171
+ return NIPC_OK;
172
+}
173
+
174
+/* ------------------------------------------------------------------ */
175
+/* Cgroups snapshot response builder */
176
+/* */
177
+/* Layout during building (max_items directory slots reserved): */
178
+/* [24-byte header space] [max_items*8 directory] [packed items] */
179
+/* */
180
+/* Layout after finish (compacted to actual item_count): */
181
+/* [24-byte header] [item_count*8 directory] [packed items] */
182
+/* */
183
+/* If item_count < max_items, finish() shifts packed data left and */
184
+/* adjusts directory offsets accordingly. */
185
+/* ------------------------------------------------------------------ */
186
+
187
+void nipc_cgroups_builder_init(nipc_cgroups_builder_t *b,
188
+ void *buf, size_t buf_len,
189
+ uint32_t max_items,
190
+ uint32_t systemd_enabled,
191
+ uint64_t generation) {
192
+ b->buf = (uint8_t *)buf;
193
+ b->buf_len = buf_len;
194
+ b->systemd_enabled = systemd_enabled;
195
+ b->generation = generation;
196
+ b->item_count = 0;
197
+ b->max_items = max_items;
198
+ b->error = NIPC_OK;
199
+
200
+ /* Packed item data starts after reserved directory */
201
+ b->data_offset = NIPC_CGROUPS_RESP_HDR_SIZE +
202
+ (size_t)max_items * NIPC_CGROUPS_DIR_ENTRY_SIZE;
203
+}
204
+
205
+void nipc_cgroups_builder_set_header(nipc_cgroups_builder_t *b,
206
+ uint32_t systemd_enabled,
207
+ uint64_t generation) {
208
+ b->systemd_enabled = systemd_enabled;
209
+ b->generation = generation;
210
+}
211
+
212
+uint32_t nipc_cgroups_builder_estimate_max_items(size_t buf_len) {
213
+ if (buf_len <= NIPC_CGROUPS_RESP_HDR_SIZE)
214
+ return 0;
215
+
216
+ size_t min_aligned_item = nipc_align8(NIPC_CGROUPS_ITEM_HDR_SIZE + 2u);
217
+ return (uint32_t)((buf_len - NIPC_CGROUPS_RESP_HDR_SIZE) /
218
+ (NIPC_CGROUPS_DIR_ENTRY_SIZE + min_aligned_item));
219
+}
220
+
221
+nipc_error_t nipc_cgroups_builder_add(nipc_cgroups_builder_t *b,
222
+ uint32_t hash,
223
+ uint32_t options,
224
+ uint32_t enabled,
225
+ const char *name, uint32_t name_len,
226
+ const char *path, uint32_t path_len) {
227
+ if (b->item_count >= b->max_items) {
228
+ b->error = NIPC_ERR_OVERFLOW;
229
+ return NIPC_ERR_OVERFLOW;
230
+ }
231
+
232
+ /* Align item start to 8 bytes */
233
+ size_t item_start = nipc_align8(b->data_offset);
234
+
235
+ /* Item payload: 32-byte header + name + NUL + path + NUL */
236
+ size_t item_size = NIPC_CGROUPS_ITEM_HDR_SIZE +
237
+ (size_t)name_len + 1 +
238
+ (size_t)path_len + 1;
239
+
240
+ if (item_start + item_size > b->buf_len) {
241
+ b->error = NIPC_ERR_OVERFLOW;
242
+ return NIPC_ERR_OVERFLOW;
243
+ }
244
+
245
+ /* Zero alignment padding */
246
+ if (item_start > b->data_offset)
247
+ memset(b->buf + b->data_offset, 0, item_start - b->data_offset);
248
+
249
+ uint8_t *item = b->buf + item_start;
250
+
251
+ /* Write item header as a single struct copy */
252
+ nipc_cgroups_item_wire_t wire = {
253
+ .layout_version = 1,
254
+ .flags = 0,
255
+ .hash = hash,
256
+ .options = options,
257
+ .enabled = enabled,
258
+ .name_offset = NIPC_CGROUPS_ITEM_HDR_SIZE,
259
+ .name_length = name_len,
260
+ .path_offset = NIPC_CGROUPS_ITEM_HDR_SIZE + name_len + 1,
261
+ .path_length = path_len,
262
+ };
263
+ memcpy(item, &wire, sizeof(wire));
264
+
265
+ /* Write strings with NUL terminators */
266
+ memcpy(item + wire.name_offset, name, name_len);
267
+ item[wire.name_offset + name_len] = '\0';
268
+ memcpy(item + wire.path_offset, path, path_len);
269
+ item[wire.path_offset + path_len] = '\0';
270
+
271
+ /* Write directory entry (absolute offset stored temporarily) */
272
+ nipc_batch_entry_t dir_entry = {
273
+ .offset = (uint32_t)item_start,
274
+ .length = (uint32_t)item_size,
275
+ };
276
+ size_t dir_pos = NIPC_CGROUPS_RESP_HDR_SIZE +
277
+ (size_t)b->item_count * NIPC_CGROUPS_DIR_ENTRY_SIZE;
278
+ memcpy(b->buf + dir_pos, &dir_entry, sizeof(dir_entry));
279
+
280
+ b->data_offset = item_start + item_size;
281
+ b->item_count++;
282
+ return NIPC_OK;
283
+}
284
+
285
+size_t nipc_cgroups_builder_finish(nipc_cgroups_builder_t *b) {
286
+ uint8_t *p = b->buf;
287
+
288
+ nipc_cgroups_resp_header_t hdr = {
289
+ .layout_version = 1,
290
+ .flags = 0,
291
+ .item_count = b->item_count,
292
+ .systemd_enabled = b->systemd_enabled,
293
+ .reserved = 0,
294
+ .generation = b->generation,
295
+ };
296
+
297
+ if (b->item_count == 0) {
298
+ memcpy(p, &hdr, sizeof(hdr));
299
+ return NIPC_CGROUPS_RESP_HDR_SIZE;
300
+ }
301
+
302
+ /* Where the decoder expects packed data to start */
303
+ size_t final_packed_start = NIPC_CGROUPS_RESP_HDR_SIZE +
304
+ (size_t)b->item_count * NIPC_CGROUPS_DIR_ENTRY_SIZE;
305
+
306
+ /* Read the first directory entry to find where packed data actually begins */
307
+ nipc_batch_entry_t first_entry;
308
+ memcpy(&first_entry, p + NIPC_CGROUPS_RESP_HDR_SIZE, sizeof(first_entry));
309
+ uint32_t first_item_abs = first_entry.offset;
310
+
311
+ /* Guard against underflow if builder state is inconsistent */
312
+ if (b->data_offset < first_item_abs) {
313
+ hdr.item_count = 0;
314
+ memcpy(p, &hdr, sizeof(hdr));
315
+ return NIPC_CGROUPS_RESP_HDR_SIZE;
316
+ }
317
+
318
+ size_t packed_data_len = b->data_offset - first_item_abs;
319
+
320
+ if (final_packed_start < first_item_abs) {
321
+ memmove(p + final_packed_start, p + first_item_abs, packed_data_len);
322
+ }
323
+
324
+ /* Convert directory entries from absolute offsets to relative offsets */
325
+ size_t dir_base = NIPC_CGROUPS_RESP_HDR_SIZE;
326
+ for (uint32_t i = 0; i < b->item_count; i++) {
327
+ size_t entry_pos = dir_base + (size_t)i * NIPC_CGROUPS_DIR_ENTRY_SIZE;
328
+ nipc_batch_entry_t entry;
329
+ memcpy(&entry, p + entry_pos, sizeof(entry));
330
+ if (entry.offset < first_item_abs)
331
+ continue; /* skip corrupted entry */
332
+ entry.offset -= first_item_abs;
333
+ memcpy(p + entry_pos, &entry, sizeof(entry));
334
+ }
335
+
336
+ /* Write snapshot header */
337
+ memcpy(p, &hdr, sizeof(hdr));
338
+
339
+ return final_packed_start + packed_data_len;
340
+}
341
+
342
+nipc_error_t nipc_dispatch_cgroups_snapshot(
343
+ const uint8_t *req, size_t req_len,
344
+ uint8_t *resp, size_t resp_size, size_t *resp_len,
345
+ uint32_t max_items,
346
+ nipc_cgroups_handler_fn handler, void *user)
347
+{
348
+ nipc_cgroups_req_t request;
349
+ nipc_error_t err = nipc_cgroups_req_decode(req, req_len, &request);
350
+ if (err != NIPC_OK)
351
+ return err;
352
+
353
+ nipc_cgroups_builder_t builder;
354
+ nipc_cgroups_builder_init(&builder, resp, resp_size, max_items, 0, 0);
355
+
356
+ if (!handler(user, &request, &builder)) {
357
+ if (builder.error != NIPC_OK)
358
+ return builder.error;
359
+ return NIPC_ERR_HANDLER_FAILED;
360
+ }
361
+
362
+ if (builder.error != NIPC_OK)
363
+ return builder.error;
364
+
365
+ *resp_len = nipc_cgroups_builder_finish(&builder);
366
+ return (*resp_len > 0) ? NIPC_OK : NIPC_ERR_OVERFLOW;
367
+}
src/libnetdata/netipc/src/protocol/netipc_protocol_cgroups_snapshot_internal.h
new
+19
@@ -0,0 +1,19 @@
1
+#ifndef NETIPC_PROTOCOL_CGROUPS_SNAPSHOT_INTERNAL_H
2
+#define NETIPC_PROTOCOL_CGROUPS_SNAPSHOT_INTERNAL_H
3
+
4
+#include "netipc_protocol_internal.h"
5
+
6
+/* Cgroups item wire header (internal, 32 bytes) */
7
+typedef struct {
8
+ uint16_t layout_version;
9
+ uint16_t flags;
10
+ uint32_t hash;
11
+ uint32_t options;
12
+ uint32_t enabled;
13
+ uint32_t name_offset;
14
+ uint32_t name_length;
15
+ uint32_t path_offset;
16
+ uint32_t path_length;
17
+} nipc_cgroups_item_wire_t;
18
+
19
+#endif /* NETIPC_PROTOCOL_CGROUPS_SNAPSHOT_INTERNAL_H */
src/libnetdata/netipc/src/protocol/netipc_protocol_increment.c
new
+37
@@ -0,0 +1,37 @@
1
+#include "netipc_protocol_internal.h"
2
+
3
+/* ------------------------------------------------------------------ */
4
+/* INCREMENT codec */
5
+/* ------------------------------------------------------------------ */
6
+
7
+size_t nipc_increment_encode(uint64_t value, void *buf, size_t buf_len) {
8
+ if (buf_len < NIPC_INCREMENT_PAYLOAD_SIZE)
9
+ return 0;
10
+ memcpy(buf, &value, 8);
11
+ return NIPC_INCREMENT_PAYLOAD_SIZE;
12
+}
13
+
14
+nipc_error_t nipc_increment_decode(const void *buf, size_t buf_len,
15
+ uint64_t *value_out) {
16
+ if (buf_len < NIPC_INCREMENT_PAYLOAD_SIZE)
17
+ return NIPC_ERR_TRUNCATED;
18
+ memcpy(value_out, buf, 8);
19
+ return NIPC_OK;
20
+}
21
+
22
+bool nipc_dispatch_increment(
23
+ const uint8_t *req, size_t req_len,
24
+ uint8_t *resp, size_t resp_size, size_t *resp_len,
25
+ nipc_increment_handler_fn handler, void *user)
26
+{
27
+ uint64_t value;
28
+ if (nipc_increment_decode(req, req_len, &value) != NIPC_OK)
29
+ return false;
30
+
31
+ uint64_t result;
32
+ if (!handler(user, value, &result))
33
+ return false;
34
+
35
+ *resp_len = nipc_increment_encode(result, resp, resp_size);
36
+ return *resp_len > 0;
37
+}
src/libnetdata/netipc/src/protocol/netipc_protocol_internal.h
new
+19
@@ -0,0 +1,19 @@
1
+#ifndef NETIPC_PROTOCOL_INTERNAL_H
2
+#define NETIPC_PROTOCOL_INTERNAL_H
3
+
4
+#include "netipc/netipc_protocol.h"
5
+
6
+#include <stddef.h>
7
+#include <string.h>
8
+
9
+/*
10
+ * Safe multiplication check: returns true if count * entry_size would
11
+ * overflow size_t. Portable across 32-bit and 64-bit without triggering
12
+ * -Wtype-limits.
13
+ */
14
+static inline bool mul_would_overflow(size_t count, size_t entry_size)
15
+{
16
+ return entry_size != 0 && count > SIZE_MAX / entry_size;
17
+}
18
+
19
+#endif /* NETIPC_PROTOCOL_INTERNAL_H */
src/libnetdata/netipc/src/protocol/netipc_protocol_lookup_common.c
new
+404
@@ -0,0 +1,404 @@
1
+#include "netipc_protocol_lookup_common.h"
2
+
3
+_Static_assert(sizeof(nipc_lookup_req_header_wire_t) == NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE,
4
+ "lookup request header must be 16 bytes");
5
+_Static_assert(sizeof(nipc_lookup_resp_header_wire_t) == NIPC_CGROUPS_LOOKUP_RESP_HDR_SIZE,
6
+ "lookup response header must be 16 bytes");
7
+_Static_assert(sizeof(nipc_lookup_dir_entry_t) == NIPC_LOOKUP_DIR_ENTRY_SIZE,
8
+ "lookup directory entry must be 8 bytes");
9
+_Static_assert(sizeof(nipc_lookup_label_entry_t) == NIPC_LOOKUP_LABEL_ENTRY_SIZE,
10
+ "lookup label entry must be 16 bytes");
11
+
12
+_Static_assert(offsetof(nipc_lookup_req_header_wire_t, layout_version) == 0, "");
13
+_Static_assert(offsetof(nipc_lookup_req_header_wire_t, flags) == 2, "");
14
+_Static_assert(offsetof(nipc_lookup_req_header_wire_t, item_count) == 4, "");
15
+_Static_assert(offsetof(nipc_lookup_req_header_wire_t, reserved0) == 8, "");
16
+_Static_assert(offsetof(nipc_lookup_req_header_wire_t, reserved1) == 12, "");
17
+
18
+_Static_assert(offsetof(nipc_lookup_resp_header_wire_t, layout_version) == 0, "");
19
+_Static_assert(offsetof(nipc_lookup_resp_header_wire_t, flags) == 2, "");
20
+_Static_assert(offsetof(nipc_lookup_resp_header_wire_t, item_count) == 4, "");
21
+_Static_assert(offsetof(nipc_lookup_resp_header_wire_t, generation) == 8, "");
22
+
23
+_Static_assert(offsetof(nipc_lookup_dir_entry_t, offset) == 0, "");
24
+_Static_assert(offsetof(nipc_lookup_dir_entry_t, length) == 4, "");
25
+_Static_assert(offsetof(nipc_lookup_label_entry_t, key_offset) == 0, "");
26
+_Static_assert(offsetof(nipc_lookup_label_entry_t, key_length) == 4, "");
27
+_Static_assert(offsetof(nipc_lookup_label_entry_t, value_offset) == 8, "");
28
+_Static_assert(offsetof(nipc_lookup_label_entry_t, value_length) == 12, "");
29
+
30
+bool nipc_lookup_bytes_have_nul(const void *ptr, uint32_t len) {
31
+ return len > 0 && memchr(ptr, '\0', len) != NULL;
32
+}
33
+
34
+bool nipc_lookup_source_string_invalid(const char *ptr, uint32_t len,
35
+ bool require_non_empty) {
36
+ if (require_non_empty && len == 0)
37
+ return true;
38
+ if (len > 0 && !ptr)
39
+ return true;
40
+ return ptr && nipc_lookup_bytes_have_nul(ptr, len);
41
+}
42
+
43
+static bool
44
+lookup_label_storage_add_u64(uint64_t *item_size,
45
+ const nipc_lookup_label_view_t *label) {
46
+ if (nipc_lookup_add_u64_over_limit(*item_size, label->key.len, UINT32_MAX,
47
+ item_size))
48
+ return false;
49
+
50
+ if (nipc_lookup_add_u64_over_limit(*item_size, 1u, UINT32_MAX, item_size))
51
+ return false;
52
+
53
+ if (nipc_lookup_add_u64_over_limit(*item_size, label->value.len, UINT32_MAX,
54
+ item_size))
55
+ return false;
56
+
57
+ if (nipc_lookup_add_u64_over_limit(*item_size, 1u, UINT32_MAX, item_size))
58
+ return false;
59
+
60
+ return true;
61
+}
62
+
63
+bool nipc_lookup_ranges_overlap_u64(uint64_t a_start, uint64_t a_end,
64
+ uint64_t b_start, uint64_t b_end) {
65
+ return a_start < b_end && b_start < a_end;
66
+}
67
+
68
+nipc_error_t nipc_lookup_string_view(const uint8_t *item, uint32_t item_len,
69
+ uint32_t hdr_size, uint32_t offset,
70
+ uint32_t length, nipc_str_view_t *out,
71
+ uint64_t *end_out) {
72
+ if (offset < hdr_size)
73
+ return NIPC_ERR_OUT_OF_BOUNDS;
74
+
75
+ uint64_t end;
76
+ if (nipc_lookup_add_u64_over_limit(offset, length, item_len, &end) ||
77
+ nipc_lookup_add_u64_over_limit(end, 1, item_len, &end))
78
+ return NIPC_ERR_OUT_OF_BOUNDS;
79
+
80
+ if (item[offset + length] != '\0')
81
+ return NIPC_ERR_MISSING_NUL;
82
+ if (nipc_lookup_bytes_have_nul(item + offset, length))
83
+ return NIPC_ERR_BAD_LAYOUT;
84
+
85
+ if (out) {
86
+ out->ptr = (const char *)(item + offset);
87
+ out->len = length;
88
+ }
89
+ if (end_out)
90
+ *end_out = end;
91
+ return NIPC_OK;
92
+}
93
+
94
+nipc_error_t nipc_lookup_validate_ordered_dir(const uint8_t *dir,
95
+ uint32_t item_count,
96
+ uint32_t packed_area_len,
97
+ uint32_t min_len, bool exact_len,
98
+ uint32_t exact_value) {
99
+ uint64_t prev_end = 0;
100
+
101
+ for (uint32_t i = 0; i < item_count; i++) {
102
+ nipc_lookup_dir_entry_t entry;
103
+ memcpy(&entry, dir + (size_t)i * NIPC_LOOKUP_DIR_ENTRY_SIZE, sizeof(entry));
104
+
105
+ if (entry.offset % NIPC_ALIGNMENT != 0)
106
+ return NIPC_ERR_BAD_ALIGNMENT;
107
+ if (exact_len && entry.length != exact_value)
108
+ return NIPC_ERR_BAD_LAYOUT;
109
+ if (!exact_len && entry.length < min_len)
110
+ return NIPC_ERR_BAD_LAYOUT;
111
+
112
+ uint64_t end;
113
+ if (nipc_lookup_add_u64_over_limit(entry.offset, entry.length,
114
+ packed_area_len, &end))
115
+ return NIPC_ERR_OUT_OF_BOUNDS;
116
+ if (i > 0 && entry.offset < prev_end)
117
+ return NIPC_ERR_BAD_LAYOUT;
118
+ prev_end = end;
119
+ }
120
+
121
+ return NIPC_OK;
122
+}
123
+
124
+nipc_error_t nipc_lookup_validate_labels(const uint8_t *item, uint32_t item_len,
125
+ uint32_t hdr_size,
126
+ uint16_t label_count,
127
+ uint64_t fixed_end,
128
+ uint32_t *label_table_offset_out) {
129
+ if (label_count == 0) {
130
+ if (fixed_end != item_len)
131
+ return NIPC_ERR_BAD_LAYOUT;
132
+ if (label_table_offset_out)
133
+ *label_table_offset_out = (uint32_t)fixed_end;
134
+ return NIPC_OK;
135
+ }
136
+
137
+ uint64_t table_start;
138
+ if (nipc_lookup_align8_u64_over_limit(fixed_end, UINT32_MAX, &table_start) ||
139
+ table_start > item_len)
140
+ return NIPC_ERR_OUT_OF_BOUNDS;
141
+
142
+ for (uint64_t i = fixed_end; i < table_start; i++) {
143
+ if (item[i] != 0)
144
+ return NIPC_ERR_BAD_LAYOUT;
145
+ }
146
+
147
+ uint64_t table_bytes = (uint64_t)label_count * NIPC_LOOKUP_LABEL_ENTRY_SIZE;
148
+ uint64_t after_table;
149
+ if (nipc_lookup_add_u64_over_limit(table_start, table_bytes, item_len,
150
+ &after_table))
151
+ return NIPC_ERR_OUT_OF_BOUNDS;
152
+
153
+ uint64_t expected = after_table;
154
+ for (uint32_t i = 0; i < label_count; i++) {
155
+ nipc_lookup_label_entry_t entry;
156
+ memcpy(&entry,
157
+ item + table_start + (uint64_t)i * NIPC_LOOKUP_LABEL_ENTRY_SIZE,
158
+ sizeof(entry));
159
+
160
+ if (entry.key_length == 0)
161
+ return NIPC_ERR_BAD_LAYOUT;
162
+ if (entry.key_offset != expected)
163
+ return NIPC_ERR_BAD_LAYOUT;
164
+
165
+ uint64_t key_end;
166
+ nipc_error_t err =
167
+ nipc_lookup_string_view(item, item_len, hdr_size, entry.key_offset,
168
+ entry.key_length, NULL, &key_end);
169
+ if (err != NIPC_OK)
170
+ return err;
171
+ expected = key_end;
172
+
173
+ if (entry.value_offset != expected)
174
+ return NIPC_ERR_BAD_LAYOUT;
175
+ uint64_t value_end;
176
+ err = nipc_lookup_string_view(item, item_len, hdr_size, entry.value_offset,
177
+ entry.value_length, NULL, &value_end);
178
+ if (err != NIPC_OK)
179
+ return err;
180
+ expected = value_end;
181
+ }
182
+
183
+ if (expected != item_len)
184
+ return NIPC_ERR_BAD_LAYOUT;
185
+ if (label_table_offset_out)
186
+ *label_table_offset_out = (uint32_t)table_start;
187
+ return NIPC_OK;
188
+}
189
+
190
+nipc_error_t nipc_lookup_label_at(const uint8_t *item, uint32_t item_len,
191
+ uint32_t hdr_size, uint16_t label_count,
192
+ uint32_t label_table_offset, uint32_t index,
193
+ nipc_lookup_label_view_t *out) {
194
+ if (index >= label_count)
195
+ return NIPC_ERR_OUT_OF_BOUNDS;
196
+
197
+ uint64_t entry_pos = (uint64_t)label_table_offset +
198
+ (uint64_t)index * NIPC_LOOKUP_LABEL_ENTRY_SIZE;
199
+ if (entry_pos + NIPC_LOOKUP_LABEL_ENTRY_SIZE > item_len)
200
+ return NIPC_ERR_OUT_OF_BOUNDS;
201
+
202
+ nipc_lookup_label_entry_t entry;
203
+ memcpy(&entry, item + entry_pos, sizeof(entry));
204
+
205
+ uint64_t ignored;
206
+ nipc_error_t err =
207
+ nipc_lookup_string_view(item, item_len, hdr_size, entry.key_offset,
208
+ entry.key_length, &out->key, &ignored);
209
+ if (err != NIPC_OK)
210
+ return err;
211
+ return nipc_lookup_string_view(item, item_len, hdr_size, entry.value_offset,
212
+ entry.value_length, &out->value, &ignored);
213
+}
214
+
215
+void nipc_lookup_write_labels(uint8_t *item, size_t table_start,
216
+ size_t table_bytes,
217
+ const nipc_lookup_label_view_t *labels,
218
+ uint16_t label_count) {
219
+ size_t next = table_start + table_bytes;
220
+ for (uint32_t i = 0; i < label_count; i++) {
221
+ nipc_lookup_label_entry_t entry = {
222
+ .key_offset = (uint32_t)next,
223
+ .key_length = labels[i].key.len,
224
+ .value_offset = (uint32_t)(next + labels[i].key.len + 1u),
225
+ .value_length = labels[i].value.len,
226
+ };
227
+ memcpy(item + table_start + (size_t)i * NIPC_LOOKUP_LABEL_ENTRY_SIZE,
228
+ &entry, sizeof(entry));
229
+ memcpy(item + entry.key_offset, labels[i].key.ptr, labels[i].key.len);
230
+ item[entry.key_offset + labels[i].key.len] = '\0';
231
+ if (labels[i].value.len > 0)
232
+ memcpy(item + entry.value_offset, labels[i].value.ptr,
233
+ labels[i].value.len);
234
+ item[entry.value_offset + labels[i].value.len] = '\0';
235
+ next = entry.value_offset + labels[i].value.len + 1u;
236
+ }
237
+}
238
+
239
+nipc_error_t nipc_lookup_builder_validate_strings(
240
+ const nipc_lookup_builder_string_t *strings, uint32_t string_count) {
241
+ for (uint32_t i = 0; i < string_count; i++) {
242
+ if (nipc_lookup_source_string_invalid(strings[i].ptr, strings[i].len,
243
+ strings[i].require_non_empty))
244
+ return NIPC_ERR_BAD_LAYOUT;
245
+ }
246
+ return NIPC_OK;
247
+}
248
+
249
+static nipc_error_t
250
+lookup_builder_layout_strings(uint32_t fixed_header_size,
251
+ nipc_lookup_builder_string_t *strings,
252
+ uint32_t string_count, uint64_t *fixed_end_out) {
253
+ uint64_t cursor = fixed_header_size;
254
+
255
+ for (uint32_t i = 0; i < string_count; i++) {
256
+ strings[i].offset = cursor;
257
+ if (nipc_lookup_add_u64_over_limit(cursor, strings[i].len, UINT32_MAX,
258
+ &cursor) ||
259
+ nipc_lookup_add_u64_over_limit(cursor, 1u, UINT32_MAX, &cursor))
260
+ return NIPC_ERR_OVERFLOW;
261
+ }
262
+
263
+ *fixed_end_out = cursor;
264
+ return NIPC_OK;
265
+}
266
+
267
+static nipc_error_t lookup_builder_layout_labels(
268
+ uint64_t fixed_end, const nipc_lookup_label_view_t *labels,
269
+ uint16_t label_count, nipc_lookup_builder_item_layout_t *layout) {
270
+ layout->table_start = fixed_end;
271
+ layout->table_bytes = 0;
272
+ layout->item_size = fixed_end;
273
+
274
+ if (label_count == 0)
275
+ return NIPC_OK;
276
+
277
+ if (!labels)
278
+ return NIPC_ERR_OVERFLOW;
279
+
280
+ if (nipc_lookup_align8_u64_over_limit(fixed_end, UINT32_MAX,
281
+ &layout->table_start))
282
+ return NIPC_ERR_OVERFLOW;
283
+
284
+ layout->table_bytes = (uint64_t)label_count * NIPC_LOOKUP_LABEL_ENTRY_SIZE;
285
+ if (nipc_lookup_add_u64_over_limit(layout->table_start, layout->table_bytes,
286
+ UINT32_MAX, &layout->item_size))
287
+ return NIPC_ERR_OVERFLOW;
288
+
289
+ for (uint32_t i = 0; i < label_count; i++) {
290
+ if (nipc_lookup_source_string_invalid(labels[i].key.ptr, labels[i].key.len,
291
+ true) ||
292
+ nipc_lookup_source_string_invalid(labels[i].value.ptr,
293
+ labels[i].value.len, false))
294
+ return NIPC_ERR_BAD_LAYOUT;
295
+
296
+ if (!lookup_label_storage_add_u64(&layout->item_size, &labels[i]))
297
+ return NIPC_ERR_OVERFLOW;
298
+ }
299
+
300
+ return NIPC_OK;
301
+}
302
+
303
+nipc_error_t nipc_lookup_builder_layout_item(
304
+ size_t data_offset, size_t buf_len, uint32_t fixed_header_size,
305
+ nipc_lookup_builder_string_t *strings, uint32_t string_count,
306
+ const nipc_lookup_label_view_t *labels, uint16_t label_count,
307
+ nipc_lookup_builder_item_layout_t *layout) {
308
+ if (nipc_lookup_align8_u64_over_limit((uint64_t)data_offset, UINT32_MAX,
309
+ &layout->item_start))
310
+ return NIPC_ERR_OVERFLOW;
311
+
312
+ nipc_error_t err = lookup_builder_layout_strings(
313
+ fixed_header_size, strings, string_count, &layout->fixed_end);
314
+ if (err != NIPC_OK)
315
+ return err;
316
+
317
+ err = lookup_builder_layout_labels(layout->fixed_end, labels, label_count,
318
+ layout);
319
+ if (err != NIPC_OK)
320
+ return err;
321
+
322
+ uint64_t buf_len_u64 = (uint64_t)buf_len;
323
+ if (layout->item_size > buf_len_u64 ||
324
+ layout->item_start > buf_len_u64 - layout->item_size)
325
+ return NIPC_ERR_OVERFLOW;
326
+
327
+ return NIPC_OK;
328
+}
329
+
330
+void nipc_lookup_builder_write_strings(
331
+ uint8_t *item, const nipc_lookup_builder_string_t *strings,
332
+ uint32_t string_count) {
333
+ for (uint32_t i = 0; i < string_count; i++) {
334
+ size_t offset = (size_t)strings[i].offset;
335
+ if (strings[i].len > 0)
336
+ memcpy(item + offset, strings[i].ptr, strings[i].len);
337
+ item[offset + strings[i].len] = '\0';
338
+ }
339
+}
340
+
341
+void nipc_lookup_builder_write_dir_entry(uint8_t *buf,
342
+ size_t response_header_size,
343
+ uint32_t item_count, size_t item_start,
344
+ size_t item_size) {
345
+ nipc_lookup_dir_entry_t dir_entry = {
346
+ .offset = (uint32_t)item_start,
347
+ .length = (uint32_t)item_size,
348
+ };
349
+ size_t dir_pos =
350
+ response_header_size + (size_t)item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
351
+ memcpy(buf + dir_pos, &dir_entry, sizeof(dir_entry));
352
+}
353
+
354
+size_t nipc_lookup_finish_common(uint8_t *p, size_t buf_len,
355
+ uint32_t item_count, size_t data_offset,
356
+ size_t header_size, uint64_t generation) {
357
+ nipc_lookup_resp_header_wire_t hdr = {
358
+ .layout_version = 1,
359
+ .flags = 0,
360
+ .item_count = item_count,
361
+ .generation = generation,
362
+ };
363
+
364
+ if (buf_len < header_size)
365
+ return 0;
366
+
367
+ if (item_count == 0) {
368
+ memcpy(p, &hdr, sizeof(hdr));
369
+ return header_size;
370
+ }
371
+
372
+ if (mul_would_overflow((size_t)item_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
373
+ return 0;
374
+ size_t dir_size = (size_t)item_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
375
+ if (header_size > SIZE_MAX - dir_size)
376
+ return 0;
377
+ size_t final_packed_start = header_size + dir_size;
378
+ nipc_lookup_dir_entry_t first_entry;
379
+ memcpy(&first_entry, p + header_size, sizeof(first_entry));
380
+ uint32_t first_item_abs = first_entry.offset;
381
+
382
+ if (data_offset < first_item_abs) {
383
+ hdr.item_count = 0;
384
+ memcpy(p, &hdr, sizeof(hdr));
385
+ return header_size;
386
+ }
387
+
388
+ size_t packed_data_len = data_offset - first_item_abs;
389
+ if (final_packed_start < first_item_abs)
390
+ memmove(p + final_packed_start, p + first_item_abs, packed_data_len);
391
+
392
+ for (uint32_t i = 0; i < item_count; i++) {
393
+ size_t entry_pos = header_size + (size_t)i * NIPC_LOOKUP_DIR_ENTRY_SIZE;
394
+ nipc_lookup_dir_entry_t entry;
395
+ memcpy(&entry, p + entry_pos, sizeof(entry));
396
+ if (entry.offset < first_item_abs)
397
+ return 0;
398
+ entry.offset -= first_item_abs;
399
+ memcpy(p + entry_pos, &entry, sizeof(entry));
400
+ }
401
+
402
+ memcpy(p, &hdr, sizeof(hdr));
403
+ return final_packed_start + packed_data_len;
404
+}
src/libnetdata/netipc/src/protocol/netipc_protocol_lookup_common.h
new
+105
@@ -0,0 +1,105 @@
1
+#ifndef NETIPC_PROTOCOL_LOOKUP_COMMON_H
2
+#define NETIPC_PROTOCOL_LOOKUP_COMMON_H
3
+
4
+#include "netipc_protocol_internal.h"
5
+
6
+typedef struct {
7
+ uint16_t layout_version;
8
+ uint16_t flags;
9
+ uint32_t item_count;
10
+ uint32_t reserved0;
11
+ uint32_t reserved1;
12
+} nipc_lookup_req_header_wire_t;
13
+
14
+typedef struct {
15
+ uint16_t layout_version;
16
+ uint16_t flags;
17
+ uint32_t item_count;
18
+ uint64_t generation;
19
+} nipc_lookup_resp_header_wire_t;
20
+
21
+typedef struct {
22
+ const char *ptr;
23
+ uint32_t len;
24
+ bool require_non_empty;
25
+ uint64_t offset;
26
+} nipc_lookup_builder_string_t;
27
+
28
+typedef struct {
29
+ uint64_t item_start;
30
+ uint64_t fixed_end;
31
+ uint64_t table_start;
32
+ uint64_t table_bytes;
33
+ uint64_t item_size;
34
+} nipc_lookup_builder_item_layout_t;
35
+
36
+static inline bool nipc_lookup_add_u64_over_limit(uint64_t a, uint64_t b,
37
+ uint64_t limit,
38
+ uint64_t *out) {
39
+ if (UINT64_MAX - a < b)
40
+ return true;
41
+ uint64_t value = a + b;
42
+ if (value > limit)
43
+ return true;
44
+ if (out)
45
+ *out = value;
46
+ return false;
47
+}
48
+
49
+static inline bool nipc_lookup_align8_u64_over_limit(uint64_t value,
50
+ uint64_t limit,
51
+ uint64_t *out) {
52
+ if (nipc_lookup_add_u64_over_limit(value, NIPC_ALIGNMENT - 1u, limit, &value))
53
+ return true;
54
+ value &= ~(uint64_t)(NIPC_ALIGNMENT - 1u);
55
+ if (out)
56
+ *out = value;
57
+ return false;
58
+}
59
+
60
+bool nipc_lookup_bytes_have_nul(const void *ptr, uint32_t len);
61
+bool nipc_lookup_source_string_invalid(const char *ptr, uint32_t len,
62
+ bool require_non_empty);
63
+bool nipc_lookup_ranges_overlap_u64(uint64_t a_start, uint64_t a_end,
64
+ uint64_t b_start, uint64_t b_end);
65
+nipc_error_t nipc_lookup_string_view(const uint8_t *item, uint32_t item_len,
66
+ uint32_t hdr_size, uint32_t offset,
67
+ uint32_t length, nipc_str_view_t *out,
68
+ uint64_t *end_out);
69
+nipc_error_t nipc_lookup_validate_ordered_dir(const uint8_t *dir,
70
+ uint32_t item_count,
71
+ uint32_t packed_area_len,
72
+ uint32_t min_len, bool exact_len,
73
+ uint32_t exact_value);
74
+nipc_error_t nipc_lookup_validate_labels(const uint8_t *item, uint32_t item_len,
75
+ uint32_t hdr_size,
76
+ uint16_t label_count,
77
+ uint64_t fixed_end,
78
+ uint32_t *label_table_offset_out);
79
+nipc_error_t nipc_lookup_label_at(const uint8_t *item, uint32_t item_len,
80
+ uint32_t hdr_size, uint16_t label_count,
81
+ uint32_t label_table_offset, uint32_t index,
82
+ nipc_lookup_label_view_t *out);
83
+void nipc_lookup_write_labels(uint8_t *item, size_t table_start,
84
+ size_t table_bytes,
85
+ const nipc_lookup_label_view_t *labels,
86
+ uint16_t label_count);
87
+nipc_error_t nipc_lookup_builder_validate_strings(
88
+ const nipc_lookup_builder_string_t *strings, uint32_t string_count);
89
+nipc_error_t nipc_lookup_builder_layout_item(
90
+ size_t data_offset, size_t buf_len, uint32_t fixed_header_size,
91
+ nipc_lookup_builder_string_t *strings, uint32_t string_count,
92
+ const nipc_lookup_label_view_t *labels, uint16_t label_count,
93
+ nipc_lookup_builder_item_layout_t *layout);
94
+void nipc_lookup_builder_write_strings(
95
+ uint8_t *item, const nipc_lookup_builder_string_t *strings,
96
+ uint32_t string_count);
97
+void nipc_lookup_builder_write_dir_entry(uint8_t *buf,
98
+ size_t response_header_size,
99
+ uint32_t item_count, size_t item_start,
100
+ size_t item_size);
101
+size_t nipc_lookup_finish_common(uint8_t *p, size_t buf_len,
102
+ uint32_t item_count, size_t data_offset,
103
+ size_t header_size, uint64_t generation);
104
+
105
+#endif /* NETIPC_PROTOCOL_LOOKUP_COMMON_H */
src/libnetdata/netipc/src/protocol/netipc_protocol_string_reverse.c
new
+74
@@ -0,0 +1,74 @@
1
+#include "netipc_protocol_internal.h"
2
+
3
+/* ------------------------------------------------------------------ */
4
+/* STRING_REVERSE codec */
5
+/* ------------------------------------------------------------------ */
6
+
7
+size_t nipc_string_reverse_encode(const char *str, uint32_t str_len,
8
+ void *buf, size_t buf_len) {
9
+ /* Guard against size_t overflow only where uint32_t can exceed size_t. */
10
+#if SIZE_MAX <= UINT32_MAX
11
+ if ((size_t)str_len > SIZE_MAX - (size_t)NIPC_STRING_REVERSE_HDR_SIZE - 1u)
12
+ return 0;
13
+#endif
14
+
15
+ size_t total = NIPC_STRING_REVERSE_HDR_SIZE + str_len + 1;
16
+ if (buf_len < total)
17
+ return 0;
18
+
19
+ uint8_t *p = (uint8_t *)buf;
20
+ uint32_t offset = NIPC_STRING_REVERSE_HDR_SIZE;
21
+ memcpy(p + 0, &offset, 4);
22
+ memcpy(p + 4, &str_len, 4);
23
+ if (str_len > 0)
24
+ memcpy(p + offset, str, str_len);
25
+ p[offset + str_len] = '\0';
26
+ return total;
27
+}
28
+
29
+nipc_error_t nipc_string_reverse_decode(const void *buf, size_t buf_len,
30
+ nipc_string_reverse_view_t *view_out) {
31
+ if (buf_len < NIPC_STRING_REVERSE_HDR_SIZE)
32
+ return NIPC_ERR_TRUNCATED;
33
+
34
+ const uint8_t *p = (const uint8_t *)buf;
35
+ uint32_t str_offset, str_length;
36
+ memcpy(&str_offset, p + 0, 4);
37
+ memcpy(&str_length, p + 4, 4);
38
+
39
+ if ((uint64_t)str_offset + str_length + 1 > buf_len)
40
+ return NIPC_ERR_OUT_OF_BOUNDS;
41
+
42
+ if (p[str_offset + str_length] != '\0')
43
+ return NIPC_ERR_MISSING_NUL;
44
+
45
+ view_out->str = (const char *)(p + str_offset);
46
+ view_out->str_len = str_length;
47
+ return NIPC_OK;
48
+}
49
+
50
+bool nipc_dispatch_string_reverse(
51
+ const uint8_t *req, size_t req_len,
52
+ uint8_t *resp, size_t resp_size, size_t *resp_len,
53
+ nipc_string_reverse_handler_fn handler, void *user)
54
+{
55
+ nipc_string_reverse_view_t view;
56
+ if (nipc_string_reverse_decode(req, req_len, &view) != NIPC_OK)
57
+ return false;
58
+
59
+ /* The handler writes the response string into scratch space that is
60
+ * already positioned at the codec payload offset. */
61
+ uint32_t capacity = (resp_size > NIPC_STRING_REVERSE_HDR_SIZE + 1)
62
+ ? (uint32_t)(resp_size - NIPC_STRING_REVERSE_HDR_SIZE - 1)
63
+ : 0;
64
+ char *scratch = (char *)(resp + NIPC_STRING_REVERSE_HDR_SIZE);
65
+
66
+ uint32_t response_str_len = 0;
67
+ if (!handler(user, view.str, view.str_len,
68
+ scratch, capacity, &response_str_len))
69
+ return false;
70
+
71
+ *resp_len = nipc_string_reverse_encode(scratch, response_str_len,
72
+ resp, resp_size);
73
+ return *resp_len > 0;
74
+}
src/libnetdata/netipc/src/service/netipc_service.c
+40
-1667
@@ -13,6 +13,9 @@
13
#include "netipc/netipc_protocol.h"
14
#include "netipc/netipc_uds.h"
15
#include "netipc/netipc_shm.h"
16
+#include "netipc_service_common.h"
17
+#include "netipc_service_platform.h"
18
+#include "netipc_service_posix_internal.h"
19
20
#include <errno.h>
21
#include <poll.h>
@@ -23,30 +26,28 @@
26
#include <time.h>
27
#include <unistd.h>
28
26
-/* Poll timeout for server loops: 100ms between shutdown checks */
27
-#define SERVER_POLL_TIMEOUT_MS 100
28
-#define NIPC_CLIENT_BUF_DEFAULT 65536u
29
#define CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS 5u
30
#define CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS 5000u
31
+#define CLIENT_CALL_RECONNECT_RETRY_INTERVAL_MS 5u
32
+#define CLIENT_CALL_RECONNECT_DRAIN_MS (SERVER_POLL_TIMEOUT_MS + 50u)
33
+#define CLIENT_CALL_RECONNECT_RETRIES 20u
34
+
35
32
-enum {
33
- NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_RESPONSE_BUF_REALLOC_INTERNAL = 1,
34
- NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL,
35
- NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SHM_CTX_CALLOC_INTERNAL,
36
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SHM_CTX_CALLOC_INTERNAL,
37
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RECV_BUF_MALLOC_INTERNAL,
38
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL,
39
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SESSIONS_CALLOC_INTERNAL,
40
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SESSION_CTX_CALLOC_INTERNAL,
41
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_THREAD_CREATE_INTERNAL,
42
- NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_BUCKETS_CALLOC_INTERNAL,
43
- NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEMS_CALLOC_INTERNAL,
44
- NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEM_NAME_MALLOC_INTERNAL,
45
- NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEM_PATH_MALLOC_INTERNAL,
46
-};
36
37
static uint64_t g_posix_service_test_fault_state = 0;
38
39
+void nipc_service_posix_sleep_us(unsigned int usec)
40
+{
41
+ struct timespec req = {
42
+ .tv_sec = (time_t)(usec / 1000000u),
43
+ .tv_nsec = (long)(usec % 1000000u) * 1000L,
44
+ };
45
+
46
+ while (nanosleep(&req, &req) == -1 && errno == EINTR) {
47
+ // Retry with the remaining interval after signal interruption.
48
+ }
49
+}
50
+
51
static uint64_t posix_service_fault_state_make(int site, uint32_t skip_matches)
52
{
53
return ((uint64_t)skip_matches << 32) | (uint32_t)site;
@@ -88,20 +89,30 @@ static bool service_test_should_fail(int site)
89
}
90
}
91
91
-static void *service_malloc(size_t size, int fault_site)
92
+void *nipc_service_posix_malloc(size_t size, int fault_site)
93
{
94
if (service_test_should_fail(fault_site))
95
return NULL;
96
return malloc(size);
97
}
98
98
-static void *service_calloc(size_t count, size_t size, int fault_site)
99
+void *nipc_service_platform_malloc(size_t size, int fault_site)
100
+{
101
+ return nipc_service_posix_malloc(size, fault_site);
102
+}
103
+
104
+void *nipc_service_posix_calloc(size_t count, size_t size, int fault_site)
105
{
106
if (service_test_should_fail(fault_site))
107
return NULL;
108
return calloc(count, size);
109
}
110
111
+void *nipc_service_platform_calloc(size_t count, size_t size, int fault_site)
112
+{
113
+ return nipc_service_posix_calloc(count, size, fault_site);
114
+}
115
+
116
static void *service_realloc(void *ptr, size_t size, int fault_site)
117
{
118
if (service_test_should_fail(fault_site))
@@ -109,7 +120,7 @@ static void *service_realloc(void *ptr, size_t size, int fault_site)
120
return realloc(ptr, size);
121
}
122
112
-static int service_pthread_create(pthread_t *thread,
123
+int nipc_service_posix_pthread_create(pthread_t *thread,
124
const pthread_attr_t *attr,
125
void *(*start_routine)(void *),
126
void *arg)
@@ -126,20 +137,12 @@ static uint64_t monotonic_time_ms(void)
137
return (uint64_t)ts.tv_sec * 1000u + (uint64_t)ts.tv_nsec / 1000000u;
138
}
139
129
-static uint32_t next_power_of_2_u32(uint32_t n)
140
+uint64_t nipc_service_platform_monotonic_ms(void)
141
{
131
- if (n < 16)
132
- return 16;
133
- n--;
134
- n |= n >> 1;
135
- n |= n >> 2;
136
- n |= n >> 4;
137
- n |= n >> 8;
138
- n |= n >> 16;
139
- return n + 1;
142
+ return monotonic_time_ms();
143
}
144
142
-static bool ensure_buffer(uint8_t **buf, size_t *buf_size, size_t need, int fault_site)
145
+bool nipc_service_posix_ensure_buffer(uint8_t **buf, size_t *buf_size, size_t need, int fault_site)
146
{
147
if (*buf && *buf_size >= need)
148
return true;
@@ -153,1641 +156,11 @@ static bool ensure_buffer(uint8_t **buf, size_t *buf_size, size_t need, int faul
156
return true;
157
}
158
156
-static bool header_payload_len(size_t payload_len, size_t *msg_len_out)
157
-{
158
-#if SIZE_MAX <= UINT32_MAX
159
- if (payload_len > SIZE_MAX - NIPC_HEADER_LEN)
160
- return false;
161
-#endif
162
-
163
- *msg_len_out = NIPC_HEADER_LEN + payload_len;
164
- return true;
165
-}
166
-
167
-static bool header_payload_len_u32(uint32_t payload_len, uint32_t *msg_len_out)
168
-{
169
- if (payload_len > UINT32_MAX - NIPC_HEADER_LEN)
170
- return false;
171
-
172
- *msg_len_out = payload_len + NIPC_HEADER_LEN;
173
- return true;
174
-}
175
-
176
-static void client_note_request_capacity(nipc_client_ctx_t *ctx, uint32_t payload_len)
177
-{
178
- uint32_t grown = next_power_of_2_u32(payload_len);
179
- if (grown > NIPC_MAX_PAYLOAD_CAP)
180
- grown = NIPC_MAX_PAYLOAD_CAP;
181
- if (grown > ctx->transport_config.max_request_payload_bytes)
182
- ctx->transport_config.max_request_payload_bytes = grown;
183
-}
184
-
185
-static void client_note_response_capacity(nipc_client_ctx_t *ctx, uint32_t payload_len)
186
-{
187
- uint32_t grown = next_power_of_2_u32(payload_len);
188
- if (grown > NIPC_MAX_PAYLOAD_CAP)
189
- grown = NIPC_MAX_PAYLOAD_CAP;
190
- if (grown > ctx->transport_config.max_response_payload_bytes)
191
- ctx->transport_config.max_response_payload_bytes = grown;
192
-}
193
-
194
-static bool client_prepare_session_buffers(nipc_client_ctx_t *ctx)
195
-{
196
- size_t response_need;
197
- if (!header_payload_len(ctx->session.max_response_payload_bytes, &response_need))
198
- return false;
199
- if (response_need < NIPC_HEADER_LEN + 1024u)
200
- response_need = NIPC_HEADER_LEN + 1024u;
201
-
202
- if (!ensure_buffer(&ctx->response_buf, &ctx->response_buf_size, response_need,
203
- NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_RESPONSE_BUF_REALLOC_INTERNAL))
204
- return false;
205
-
206
- if (ctx->session.selected_profile == NIPC_PROFILE_SHM_HYBRID ||
207
- ctx->session.selected_profile == NIPC_PROFILE_SHM_FUTEX) {
208
- size_t send_need;
209
- if (!header_payload_len(ctx->session.max_request_payload_bytes, &send_need))
210
- return false;
211
- if (!ensure_buffer(&ctx->send_buf, &ctx->send_buf_size, send_need,
212
- NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL))
213
- return false;
214
- }
215
-
216
- return true;
217
-}
218
-
219
-static uint32_t cgroups_request_payload_default(void)
220
-{
221
- return 16u;
222
-}
223
-
224
-static uint32_t cgroups_response_payload_default(void)
225
-{
226
- return NIPC_CLIENT_BUF_DEFAULT;
227
-}
228
-
229
-static nipc_uds_client_config_t service_client_config_to_transport(
230
- const nipc_client_config_t *config)
231
-{
232
- nipc_uds_client_config_t transport = {0};
233
-
234
- if (!config)
235
- return transport;
236
-
237
- transport.supported_profiles = config->supported_profiles;
238
- transport.preferred_profiles = config->preferred_profiles;
239
- transport.max_request_batch_items = config->max_request_batch_items;
240
- transport.max_response_payload_bytes = config->max_response_payload_bytes;
241
- transport.max_response_batch_items = config->max_request_batch_items;
242
- transport.auth_token = config->auth_token;
243
-
244
- return transport;
245
-}
246
-
247
-static nipc_uds_server_config_t service_server_config_to_transport(
248
- const nipc_server_config_t *config)
249
-{
250
- nipc_uds_server_config_t transport = {0};
251
-
252
- if (!config)
253
- return transport;
254
-
255
- transport.supported_profiles = config->supported_profiles;
256
- transport.preferred_profiles = config->preferred_profiles;
257
- transport.max_request_batch_items = config->max_request_batch_items;
258
- transport.max_response_payload_bytes = config->max_response_payload_bytes;
259
- transport.max_response_batch_items = config->max_request_batch_items;
260
- transport.auth_token = config->auth_token;
261
-
262
- return transport;
263
-}
264
-
265
-/* ------------------------------------------------------------------ */
266
-/* Internal: client connection helpers */
267
-/* ------------------------------------------------------------------ */
268
-
269
-/* Tear down the current connection (UDS session + SHM if any). */
270
-static void client_disconnect(nipc_client_ctx_t *ctx)
271
-{
272
- if (ctx->shm) {
273
- nipc_shm_close(ctx->shm);
274
- free(ctx->shm);
275
- ctx->shm = NULL;
276
- }
277
-
278
- if (ctx->session_valid) {
279
- nipc_uds_close_session(&ctx->session);
280
- ctx->session_valid = false;
281
- }
282
-}
283
-
284
-static void client_disable_shm_profiles(nipc_client_ctx_t *ctx)
285
-{
286
- ctx->transport_config.supported_profiles &=
287
- ~(NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
288
- ctx->transport_config.preferred_profiles &=
289
- ~(NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
290
-}
291
-
292
-/* Attempt a full connection: UDS connect + handshake, then SHM upgrade
293
- * if negotiated. Returns the new state. */
294
-static nipc_client_state_t client_try_connect(nipc_client_ctx_t *ctx)
295
-{
296
- nipc_uds_session_t session;
297
- memset(&session, 0, sizeof(session));
298
- session.fd = -1;
299
-
300
- nipc_uds_error_t err = nipc_uds_connect(
301
- ctx->run_dir, ctx->service_name,
302
- &ctx->transport_config, &session);
303
-
304
- switch (err) {
305
- case NIPC_UDS_OK:
306
- break;
307
- case NIPC_UDS_ERR_CONNECT:
308
- return NIPC_CLIENT_NOT_FOUND;
309
- case NIPC_UDS_ERR_AUTH_FAILED:
310
- return NIPC_CLIENT_AUTH_FAILED;
311
- case NIPC_UDS_ERR_NO_PROFILE:
312
- case NIPC_UDS_ERR_INCOMPATIBLE:
313
- return NIPC_CLIENT_INCOMPATIBLE;
314
- default:
315
- return NIPC_CLIENT_DISCONNECTED;
316
- }
317
-
318
- ctx->session = session;
319
- ctx->session_valid = true;
320
-
321
- if (!client_prepare_session_buffers(ctx)) {
322
- nipc_uds_close_session(&ctx->session);
323
- ctx->session_valid = false;
324
- return NIPC_CLIENT_DISCONNECTED;
325
- }
326
-
327
- /* SHM upgrade if negotiated */
328
- if (session.selected_profile == NIPC_PROFILE_SHM_HYBRID ||
329
- session.selected_profile == NIPC_PROFILE_SHM_FUTEX) {
330
-
331
- nipc_shm_ctx_t *shm = service_calloc(
332
- 1, sizeof(nipc_shm_ctx_t),
333
- NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SHM_CTX_CALLOC_INTERNAL);
334
- if (!shm) {
335
- nipc_uds_close_session(&ctx->session);
336
- ctx->session_valid = false;
337
- return NIPC_CLIENT_DISCONNECTED;
338
- }
339
- {
340
- /* Retry attach: server creates the SHM region after
341
- * the UDS handshake, so it may not exist yet. */
342
- nipc_shm_error_t serr = NIPC_SHM_ERR_NOT_READY;
343
- uint64_t deadline_ms = monotonic_time_ms() + CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS;
344
- for (;;) {
345
- serr = nipc_shm_client_attach(
346
- ctx->run_dir, ctx->service_name,
347
- session.session_id, shm);
348
- if (serr == NIPC_SHM_OK)
349
- break;
350
- if (serr != NIPC_SHM_ERR_NOT_READY &&
351
- serr != NIPC_SHM_ERR_OPEN &&
352
- serr != NIPC_SHM_ERR_BAD_MAGIC)
353
- break;
354
- if (monotonic_time_ms() >= deadline_ms)
355
- break;
356
- usleep(CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS * 1000u);
357
- }
358
-
359
- if (serr == NIPC_SHM_OK) {
360
- ctx->shm = shm;
361
- } else {
362
- /* SHM attach failed after negotiation. Close that session,
363
- * blacklist SHM for this client context, and retry
364
- * baseline via a new handshake. */
365
- free(shm);
366
- nipc_uds_close_session(&ctx->session);
367
- ctx->session_valid = false;
368
- client_disable_shm_profiles(ctx);
369
- if (ctx->transport_config.supported_profiles == 0)
370
- return NIPC_CLIENT_DISCONNECTED;
371
- return client_try_connect(ctx);
372
- }
373
- }
374
- }
375
-
376
- return NIPC_CLIENT_READY;
377
-}
378
-
379
-/* ------------------------------------------------------------------ */
380
-/* Internal: send/receive via the active transport */
381
-/* ------------------------------------------------------------------ */
382
-
383
-/*
384
- * Send a complete message (header + payload) using whichever transport
385
- * is active: SHM if negotiated, UDS otherwise.
386
- */
387
-static nipc_error_t transport_send(nipc_client_ctx_t *ctx,
388
- nipc_header_t *hdr,
389
- const void *payload,
390
- size_t payload_len)
391
-{
392
- if (payload_len > UINT32_MAX)
393
- return NIPC_ERR_OVERFLOW;
394
-
395
- if (ctx->shm) {
396
- if (payload_len > ctx->session.max_request_payload_bytes) {
397
- client_note_request_capacity(ctx, (uint32_t)payload_len);
398
- return NIPC_ERR_OVERFLOW;
399
- }
400
-
401
- size_t msg_len;
402
- if (!header_payload_len(payload_len, &msg_len))
403
- return NIPC_ERR_OVERFLOW;
404
-
405
- uint8_t *msg = ctx->send_buf;
406
- if (!msg || msg_len > ctx->send_buf_size)
407
- return NIPC_ERR_OVERFLOW;
408
-
409
- hdr->magic = NIPC_MAGIC_MSG;
410
- hdr->version = NIPC_VERSION;
411
- hdr->header_len = NIPC_HEADER_LEN;
412
- hdr->payload_len = (uint32_t)payload_len;
413
-
414
- nipc_header_encode(hdr, msg, NIPC_HEADER_LEN);
415
- if (payload_len > 0)
416
- memcpy(msg + NIPC_HEADER_LEN, payload, payload_len);
417
-
418
- nipc_shm_error_t serr = nipc_shm_send(ctx->shm, msg, msg_len);
419
- if (serr == NIPC_SHM_ERR_MSG_TOO_LARGE) {
420
- client_note_request_capacity(ctx, (uint32_t)payload_len);
421
- return NIPC_ERR_OVERFLOW;
422
- }
423
- return (serr == NIPC_SHM_OK) ? NIPC_OK : NIPC_ERR_NOT_READY;
424
- }
425
-
426
- /* UDS path */
427
- nipc_uds_error_t uerr = nipc_uds_send(&ctx->session, hdr,
428
- payload, payload_len);
429
- if (uerr == NIPC_UDS_ERR_LIMIT_EXCEEDED) {
430
- client_note_request_capacity(ctx, (uint32_t)payload_len);
431
- return NIPC_ERR_OVERFLOW;
432
- }
433
- return (uerr == NIPC_UDS_OK) ? NIPC_OK : NIPC_ERR_NOT_READY;
434
-}
435
-
436
-/*
437
- * Receive a complete message. For SHM, reads from the SHM region.
438
- * For UDS, reads from the socket into the caller's buffer.
439
- *
440
- * On success, hdr_out is filled, and payload_out + payload_len_out
441
- * point to the payload bytes (valid until next receive).
442
- */
443
-static nipc_error_t transport_receive(nipc_client_ctx_t *ctx,
444
- void *buf, size_t buf_size,
445
- nipc_header_t *hdr_out,
446
- const void **payload_out,
447
- size_t *payload_len_out)
448
-{
449
- if (ctx->shm) {
450
- size_t msg_len;
451
- nipc_shm_error_t serr = nipc_shm_receive(ctx->shm, buf, buf_size,
452
- &msg_len, 30000);
453
- if (serr != NIPC_SHM_OK)
454
- return NIPC_ERR_TRUNCATED;
455
-
456
- if (msg_len < NIPC_HEADER_LEN)
457
- return NIPC_ERR_TRUNCATED;
458
-
459
- nipc_error_t perr = nipc_header_decode(buf, msg_len, hdr_out);
460
- if (perr != NIPC_OK)
461
- return perr;
462
-
463
- *payload_out = (const uint8_t *)buf + NIPC_HEADER_LEN;
464
- *payload_len_out = msg_len - NIPC_HEADER_LEN;
465
- return NIPC_OK;
466
- }
467
-
468
- /* UDS path */
469
- nipc_uds_error_t uerr = nipc_uds_receive(&ctx->session, buf, buf_size,
470
- hdr_out, payload_out,
471
- payload_len_out);
472
- return (uerr == NIPC_UDS_OK) ? NIPC_OK : NIPC_ERR_TRUNCATED;
473
-}
474
-
475
-/* ------------------------------------------------------------------ */
476
-/* Internal: generic raw call (send request, receive response) */
477
-/* ------------------------------------------------------------------ */
478
-
479
-/*
480
- * Single-attempt raw call: build envelope, send, receive, validate
481
- * envelope. The caller handles encode before and decode after.
482
- *
483
- * On success, response_payload_out and response_len_out point into the
484
- * internal client response buffer (valid until next call on this context).
485
- */
486
-static nipc_error_t do_raw_call(nipc_client_ctx_t *ctx,
487
- uint16_t method_code,
488
- const void *request_payload,
489
- size_t request_len,
490
- const void **response_payload_out,
491
- size_t *response_len_out)
492
-{
493
- nipc_header_t hdr = {0};
494
- hdr.kind = NIPC_KIND_REQUEST;
495
- hdr.code = method_code;
496
- hdr.flags = 0;
497
- hdr.item_count = 1;
498
- hdr.message_id = (uint64_t)(ctx->call_count + 1);
499
- hdr.transport_status = NIPC_STATUS_OK;
500
-
501
- nipc_error_t err = transport_send(ctx, &hdr, request_payload, request_len);
502
- if (err != NIPC_OK) {
503
- return err;
504
- }
505
-
506
- nipc_header_t resp_hdr;
507
- err = transport_receive(ctx, ctx->response_buf, ctx->response_buf_size,
508
- &resp_hdr, response_payload_out, response_len_out);
509
- if (err != NIPC_OK) {
510
- return err;
511
- }
512
-
513
- if (resp_hdr.kind != NIPC_KIND_RESPONSE)
514
- return NIPC_ERR_BAD_KIND;
515
- if (resp_hdr.code != method_code)
516
- return NIPC_ERR_BAD_LAYOUT;
517
- if (resp_hdr.message_id != hdr.message_id)
518
- return NIPC_ERR_BAD_LAYOUT;
519
-
520
- switch (resp_hdr.transport_status) {
521
- case NIPC_STATUS_OK:
522
- break;
523
- case NIPC_STATUS_LIMIT_EXCEEDED:
524
- if (ctx->session.max_response_payload_bytes > 0) {
525
- uint32_t current = ctx->session.max_response_payload_bytes;
526
- client_note_response_capacity(
527
- ctx, current >= UINT32_MAX / 2u ? UINT32_MAX : current * 2u);
528
- }
529
- return NIPC_ERR_OVERFLOW;
530
- case NIPC_STATUS_UNSUPPORTED:
531
- return NIPC_ERR_BAD_LAYOUT;
532
- case NIPC_STATUS_BAD_ENVELOPE:
533
- case NIPC_STATUS_INTERNAL_ERROR:
534
- default:
535
- return NIPC_ERR_BAD_LAYOUT;
536
- }
537
-
538
- return NIPC_OK;
539
-}
540
-
541
-/*
542
- * Generic call-with-retry:
543
- * - ordinary failures reconnect and retry once
544
- * - overflow-driven resize recovery may reconnect repeatedly until
545
- * negotiated capacities grow or recovery fails
546
- * The caller provides a function pointer for the single-attempt logic.
547
- */
548
-typedef nipc_error_t (*nipc_attempt_fn)(nipc_client_ctx_t *ctx, void *state);
549
-
550
-static nipc_error_t call_with_retry(nipc_client_ctx_t *ctx,
551
- nipc_attempt_fn attempt,
552
- void *state)
553
-{
554
- if (ctx->state != NIPC_CLIENT_READY) {
555
- ctx->error_count++;
556
- return NIPC_ERR_NOT_READY;
557
- }
558
-
559
- /* Cap overflow-driven retries: payloads grow by powers of 2, so 8
560
- * retries allows ~256x growth from the initial negotiated size. */
561
- int overflow_retries = 0;
562
- for (;;) {
563
- uint32_t prev_req = ctx->session.max_request_payload_bytes;
564
- uint32_t prev_resp = ctx->session.max_response_payload_bytes;
565
- uint32_t prev_cfg_req = ctx->transport_config.max_request_payload_bytes;
566
- uint32_t prev_cfg_resp = ctx->transport_config.max_response_payload_bytes;
567
-
568
- nipc_error_t err = attempt(ctx, state);
569
- if (err == NIPC_OK) {
570
- ctx->call_count++;
571
- return NIPC_OK;
572
- }
573
-
574
- if (err != NIPC_ERR_OVERFLOW) {
575
- client_disconnect(ctx);
576
- ctx->state = NIPC_CLIENT_BROKEN;
577
- ctx->state = client_try_connect(ctx);
578
- if (ctx->state != NIPC_CLIENT_READY) {
579
- ctx->error_count++;
580
- return err;
581
- }
582
-
583
- ctx->reconnect_count++;
584
- err = attempt(ctx, state);
585
- if (err == NIPC_OK) {
586
- ctx->call_count++;
587
- return NIPC_OK;
588
- }
589
-
590
- client_disconnect(ctx);
591
- ctx->state = NIPC_CLIENT_BROKEN;
592
- ctx->error_count++;
593
- return err;
594
- }
595
-
596
- client_disconnect(ctx);
597
- ctx->state = NIPC_CLIENT_BROKEN;
598
- ctx->state = client_try_connect(ctx);
599
- if (ctx->state != NIPC_CLIENT_READY) {
600
- ctx->error_count++;
601
- return err;
602
- }
603
- ctx->reconnect_count++;
604
-
605
- if (ctx->session.max_request_payload_bytes <= prev_req &&
606
- ctx->session.max_response_payload_bytes <= prev_resp &&
607
- ctx->transport_config.max_request_payload_bytes <= prev_cfg_req &&
608
- ctx->transport_config.max_response_payload_bytes <= prev_cfg_resp) {
609
- client_disconnect(ctx);
610
- ctx->state = NIPC_CLIENT_BROKEN;
611
- ctx->error_count++;
612
- return err;
613
- }
614
-
615
- if (++overflow_retries >= 8) {
616
- client_disconnect(ctx);
617
- ctx->state = NIPC_CLIENT_BROKEN;
618
- ctx->error_count++;
619
- return err;
620
- }
621
- }
622
-}
623
-
624
-/* ------------------------------------------------------------------ */
625
-/* Internal: single attempt at a cgroups snapshot call */
626
-/* ------------------------------------------------------------------ */
627
-
628
-typedef struct {
629
- nipc_cgroups_resp_view_t *view_out;
630
-} cgroups_call_state_t;
631
-
632
-static nipc_error_t do_cgroups_attempt(nipc_client_ctx_t *ctx, void *state)
633
-{
634
- cgroups_call_state_t *s = (cgroups_call_state_t *)state;
635
-
636
- nipc_cgroups_req_t req = { .layout_version = 1, .flags = 0 };
637
- uint8_t req_buf[4];
638
- size_t req_len = nipc_cgroups_req_encode(&req, req_buf, sizeof(req_buf));
639
- if (req_len == 0)
640
- return NIPC_ERR_TRUNCATED;
641
-
642
- const void *payload;
643
- size_t payload_len;
644
- nipc_error_t err = do_raw_call(ctx, NIPC_METHOD_CGROUPS_SNAPSHOT,
645
- req_buf, req_len,
646
- &payload, &payload_len);
647
- if (err != NIPC_OK)
648
- return err;
649
-
650
- return nipc_cgroups_resp_decode(payload, payload_len, s->view_out);
651
-}
652
-
653
-/* ------------------------------------------------------------------ */
654
-/* Public API: client lifecycle */
655
-/* ------------------------------------------------------------------ */
656
-
657
-void nipc_client_init(nipc_client_ctx_t *ctx,
658
- const char *run_dir,
659
- const char *service_name,
660
- const nipc_client_config_t *config)
661
-{
662
- memset(ctx, 0, sizeof(*ctx));
663
- ctx->state = NIPC_CLIENT_DISCONNECTED;
664
- ctx->session.fd = -1;
665
- ctx->session_valid = false;
666
- ctx->shm = NULL;
667
-
668
- if (run_dir) {
669
- size_t len = strlen(run_dir);
670
- if (len >= sizeof(ctx->run_dir))
671
- len = sizeof(ctx->run_dir) - 1;
672
- memcpy(ctx->run_dir, run_dir, len);
673
- ctx->run_dir[len] = '\0';
674
- }
675
-
676
- if (service_name) {
677
- size_t len = strlen(service_name);
678
- if (len >= sizeof(ctx->service_name))
679
- len = sizeof(ctx->service_name) - 1;
680
- memcpy(ctx->service_name, service_name, len);
681
- ctx->service_name[len] = '\0';
682
- }
683
-
684
- ctx->transport_config = service_client_config_to_transport(config);
685
- if (ctx->transport_config.max_request_payload_bytes == 0)
686
- ctx->transport_config.max_request_payload_bytes = cgroups_request_payload_default();
687
- if (ctx->transport_config.max_response_payload_bytes == 0)
688
- ctx->transport_config.max_response_payload_bytes = cgroups_response_payload_default();
689
-}
690
-
691
-bool nipc_client_refresh(nipc_client_ctx_t *ctx)
692
-{
693
- nipc_client_state_t old_state = ctx->state;
694
-
695
- switch (ctx->state) {
696
- case NIPC_CLIENT_DISCONNECTED:
697
- case NIPC_CLIENT_NOT_FOUND:
698
- /* Attempt to connect */
699
- ctx->state = NIPC_CLIENT_CONNECTING;
700
- ctx->state = client_try_connect(ctx);
701
- if (ctx->state == NIPC_CLIENT_READY)
702
- ctx->connect_count++;
703
- break;
704
-
705
- case NIPC_CLIENT_BROKEN:
706
- /* Reconnect: tear down old connection first */
707
- client_disconnect(ctx);
708
- ctx->state = NIPC_CLIENT_CONNECTING;
709
- ctx->state = client_try_connect(ctx);
710
- if (ctx->state == NIPC_CLIENT_READY)
711
- ctx->reconnect_count++;
712
- break;
713
-
714
- case NIPC_CLIENT_READY:
715
- case NIPC_CLIENT_CONNECTING:
716
- case NIPC_CLIENT_AUTH_FAILED:
717
- case NIPC_CLIENT_INCOMPATIBLE:
718
- /* No action needed */
719
- break;
720
- }
721
-
722
- return ctx->state != old_state;
723
-}
724
-
725
-void nipc_client_status(const nipc_client_ctx_t *ctx,
726
- nipc_client_status_t *out)
159
+bool nipc_service_platform_ensure_client_send_buffer(nipc_client_ctx_t *ctx,
160
+ size_t need)
161
{
728
- out->state = ctx->state;
729
- out->connect_count = ctx->connect_count;
730
- out->reconnect_count = ctx->reconnect_count;
731
- out->call_count = ctx->call_count;
732
- out->error_count = ctx->error_count;
162
+ return nipc_service_posix_ensure_buffer(&ctx->send_buf, &ctx->send_buf_size, need,
163
+ NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL);
164
}
165
735
-void nipc_client_close(nipc_client_ctx_t *ctx)
736
-{
737
- client_disconnect(ctx);
738
- free(ctx->response_buf);
739
- free(ctx->send_buf);
740
- ctx->response_buf = NULL;
741
- ctx->send_buf = NULL;
742
- ctx->response_buf_size = 0;
743
- ctx->send_buf_size = 0;
744
- ctx->state = NIPC_CLIENT_DISCONNECTED;
745
-}
746
-
747
-/* ------------------------------------------------------------------ */
748
-/* Public API: typed cgroups snapshot call */
749
-/* ------------------------------------------------------------------ */
750
-
751
-nipc_error_t nipc_client_call_cgroups_snapshot(
752
- nipc_client_ctx_t *ctx,
753
- nipc_cgroups_resp_view_t *view_out)
754
-{
755
- cgroups_call_state_t state = {
756
- .view_out = view_out,
757
- };
758
- return call_with_retry(ctx, do_cgroups_attempt, &state);
759
-}
760
-
761
-/* ------------------------------------------------------------------ */
762
-/* Internal: managed server session handler */
763
-/* ------------------------------------------------------------------ */
764
-
765
-/*
766
- * Wait for data on a file descriptor with periodic shutdown checks.
767
- * Returns: 1 = data ready, 0 = server stopping, -1 = error/hangup.
768
- */
769
-static int poll_with_shutdown(int fd, bool *running)
770
-{
771
- while (__atomic_load_n(running, __ATOMIC_RELAXED)) {
772
- struct pollfd pfd = { .fd = fd, .events = POLLIN };
773
- int ret = poll(&pfd, 1, SERVER_POLL_TIMEOUT_MS);
774
-
775
- if (ret < 0) {
776
- if (errno == EINTR)
777
- continue;
778
- return -1;
779
- }
780
-
781
- if (ret == 0)
782
- continue; /* timeout, check running flag */
783
-
784
- if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL))
785
- return -1;
786
-
787
- if (pfd.revents & POLLIN)
788
- return 1;
789
- }
790
- return 0;
791
-}
792
-
793
-static uint32_t server_snapshot_max_items(size_t response_buf_size,
794
- const nipc_cgroups_service_handler_t *service_handler)
795
-{
796
- if (service_handler->snapshot_max_items != 0)
797
- return service_handler->snapshot_max_items;
798
- return nipc_cgroups_builder_estimate_max_items(response_buf_size);
799
-}
800
-
801
-static void server_note_request_capacity(nipc_managed_server_t *server,
802
- uint32_t payload_len)
803
-{
804
- uint32_t grown = next_power_of_2_u32(payload_len);
805
- uint32_t current = __atomic_load_n(&server->learned_request_payload_bytes,
806
- __ATOMIC_RELAXED);
807
- while (grown > current &&
808
- !__atomic_compare_exchange_n(&server->learned_request_payload_bytes,
809
- ¤t, grown, false,
810
- __ATOMIC_RELEASE, __ATOMIC_RELAXED)) {
811
- }
812
-}
813
-
814
-static void server_note_response_capacity(nipc_managed_server_t *server,
815
- uint32_t payload_len)
816
-{
817
- uint32_t grown = next_power_of_2_u32(payload_len);
818
- uint32_t current = __atomic_load_n(&server->learned_response_payload_bytes,
819
- __ATOMIC_RELAXED);
820
- while (grown > current &&
821
- !__atomic_compare_exchange_n(&server->learned_response_payload_bytes,
822
- ¤t, grown, false,
823
- __ATOMIC_RELEASE, __ATOMIC_RELAXED)) {
824
- }
825
-}
826
-
827
-static nipc_error_t server_typed_dispatch(void *user,
828
- const nipc_header_t *request_hdr,
829
- const uint8_t *request_payload,
830
- size_t request_len,
831
- uint8_t *response_buf,
832
- size_t response_buf_size,
833
- size_t *response_len_out)
834
-{
835
- nipc_managed_server_t *server = (nipc_managed_server_t *)user;
836
- nipc_cgroups_service_handler_t *service_handler = &server->service_handler;
837
- (void)request_hdr;
838
-
839
- if (!service_handler->handle)
840
- return NIPC_ERR_HANDLER_FAILED;
841
-
842
- return nipc_dispatch_cgroups_snapshot(
843
- request_payload, request_len,
844
- response_buf, response_buf_size, response_len_out,
845
- server_snapshot_max_items(response_buf_size, service_handler),
846
- service_handler->handle, service_handler->user);
847
-}
848
-
849
-/*
850
- * Handle one client session: read requests, dispatch to handler,
851
- * send responses. Each session gets its own response buffer.
852
- * Runs until the client disconnects or server stops.
853
- */
854
-static void server_handle_session(nipc_managed_server_t *server,
855
- nipc_uds_session_t *session,
856
- nipc_shm_ctx_t *shm,
857
- uint8_t *resp_buf,
858
- size_t resp_buf_size)
859
-{
860
- /* Allocate recv buffer based on negotiated max request size */
861
- size_t recv_size;
862
- if (!header_payload_len(session->max_request_payload_bytes, &recv_size))
863
- return;
864
- if (recv_size < NIPC_HEADER_LEN + 1024u)
865
- recv_size = NIPC_HEADER_LEN + 1024u;
866
- uint8_t *recv_buf = service_malloc(
867
- recv_size, NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RECV_BUF_MALLOC_INTERNAL);
868
- if (!recv_buf)
869
- return;
870
-
871
- while (__atomic_load_n(&server->running, __ATOMIC_RELAXED)) {
872
- nipc_header_t hdr;
873
- const void *payload;
874
- size_t payload_len;
875
-
876
- /* Receive request via the active transport */
877
- if (shm) {
878
- size_t msg_len;
879
- nipc_shm_error_t serr = nipc_shm_receive(shm, recv_buf, recv_size,
880
- &msg_len, SERVER_POLL_TIMEOUT_MS);
881
- if (serr == NIPC_SHM_ERR_TIMEOUT)
882
- continue; /* check running flag */
883
- if (serr != NIPC_SHM_OK)
884
- break;
885
- if (msg_len < NIPC_HEADER_LEN)
886
- break;
887
-
888
- nipc_error_t perr = nipc_header_decode(recv_buf, msg_len, &hdr);
889
- if (perr != NIPC_OK)
890
- break;
891
-
892
- payload = recv_buf + NIPC_HEADER_LEN;
893
- payload_len = msg_len - NIPC_HEADER_LEN;
894
- } else {
895
- /* Poll the session fd before blocking on receive */
896
- int pr = poll_with_shutdown(session->fd, &server->running);
897
- if (pr <= 0)
898
- break; /* shutdown or error */
899
-
900
- nipc_uds_error_t uerr = nipc_uds_receive(
901
- session, recv_buf, recv_size,
902
- &hdr, &payload, &payload_len);
903
- if (uerr == NIPC_UDS_ERR_LIMIT_EXCEEDED) {
904
- if (hdr.kind == NIPC_KIND_REQUEST) {
905
- if (hdr.payload_len > 0)
906
- server_note_request_capacity(server, hdr.payload_len);
907
-
908
- nipc_header_t resp_hdr = {0};
909
- resp_hdr.kind = NIPC_KIND_RESPONSE;
910
- resp_hdr.code = hdr.code;
911
- resp_hdr.message_id = hdr.message_id;
912
- resp_hdr.transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
913
- resp_hdr.item_count = 1;
914
- resp_hdr.flags = 0;
915
-
916
- if (nipc_uds_send(session, &resp_hdr, NULL, 0) != NIPC_UDS_OK)
917
- break;
918
- }
919
- break;
920
- }
921
- if (uerr != NIPC_UDS_OK)
922
- break;
923
- }
924
-
925
- /* Protocol violation: unexpected message kind terminates session */
926
- if (hdr.kind != NIPC_KIND_REQUEST)
927
- break;
928
-
929
- if (hdr.code != server->expected_method_code) {
930
- nipc_header_t resp_hdr = {0};
931
- resp_hdr.kind = NIPC_KIND_RESPONSE;
932
- resp_hdr.code = hdr.code;
933
- resp_hdr.message_id = hdr.message_id;
934
- resp_hdr.transport_status = NIPC_STATUS_UNSUPPORTED;
935
- resp_hdr.item_count = 1;
936
- resp_hdr.flags = 0;
937
-
938
- if (shm) {
939
- uint8_t msg[NIPC_HEADER_LEN];
940
- resp_hdr.magic = NIPC_MAGIC_MSG;
941
- resp_hdr.version = NIPC_VERSION;
942
- resp_hdr.header_len = NIPC_HEADER_LEN;
943
- resp_hdr.payload_len = 0;
944
- nipc_header_encode(&resp_hdr, msg, sizeof(msg));
945
- if (nipc_shm_send(shm, msg, sizeof(msg)) != NIPC_SHM_OK)
946
- break;
947
- } else {
948
- if (nipc_uds_send(session, &resp_hdr, NULL, 0) != NIPC_UDS_OK)
949
- break;
950
- }
951
- continue;
952
- }
953
-
954
- if (payload_len <= UINT32_MAX)
955
- server_note_request_capacity(server, (uint32_t)payload_len);
956
-
957
- /* Dispatch: one request kind per service endpoint. */
958
- size_t response_len = 0;
959
- nipc_error_t dispatch_err = server->handler(
960
- server->handler_user,
961
- &hdr,
962
- (const uint8_t *)payload, payload_len,
963
- resp_buf, resp_buf_size,
964
- &response_len);
965
-
966
- /* Build response header */
967
- nipc_header_t resp_hdr = {0};
968
- resp_hdr.kind = NIPC_KIND_RESPONSE;
969
- resp_hdr.code = hdr.code;
970
- resp_hdr.message_id = hdr.message_id;
971
- if ((hdr.flags & NIPC_FLAG_BATCH) && hdr.item_count >= 1) {
972
- resp_hdr.item_count = hdr.item_count;
973
- resp_hdr.flags = NIPC_FLAG_BATCH;
974
- } else {
975
- resp_hdr.item_count = 1;
976
- resp_hdr.flags = 0;
977
- }
978
-
979
- switch (dispatch_err) {
980
- case NIPC_OK:
981
- if (response_len > resp_buf_size ||
982
- response_len > session->max_response_payload_bytes ||
983
- response_len > SIZE_MAX - NIPC_HEADER_LEN) {
984
- server_note_response_capacity(
985
- server,
986
- response_len >= UINT32_MAX ? UINT32_MAX : (uint32_t)response_len);
987
- resp_hdr.transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
988
- response_len = 0;
989
- } else {
990
- if (response_len <= UINT32_MAX)
991
- server_note_response_capacity(server, (uint32_t)response_len);
992
- resp_hdr.transport_status = NIPC_STATUS_OK;
993
- }
994
- break;
995
- case NIPC_ERR_OVERFLOW:
996
- if (session->max_response_payload_bytes >= UINT32_MAX / 2u)
997
- server_note_response_capacity(server, UINT32_MAX);
998
- else
999
- server_note_response_capacity(server,
1000
- session->max_response_payload_bytes * 2u);
1001
- resp_hdr.transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
1002
- response_len = 0;
1003
- break;
1004
- case NIPC_ERR_TRUNCATED:
1005
- case NIPC_ERR_BAD_LAYOUT:
1006
- case NIPC_ERR_OUT_OF_BOUNDS:
1007
- case NIPC_ERR_MISSING_NUL:
1008
- case NIPC_ERR_BAD_ALIGNMENT:
1009
- case NIPC_ERR_BAD_ITEM_COUNT:
1010
- resp_hdr.transport_status = NIPC_STATUS_BAD_ENVELOPE;
1011
- response_len = 0;
1012
- break;
1013
- case NIPC_ERR_HANDLER_FAILED:
1014
- default:
1015
- resp_hdr.transport_status = NIPC_STATUS_INTERNAL_ERROR;
1016
- response_len = 0;
1017
- break;
1018
- }
1019
-
1020
- /* Send response via the active transport */
1021
- if (shm) {
1022
- size_t msg_len;
1023
- if (!header_payload_len(response_len, &msg_len))
1024
- break;
1025
-
1026
- resp_hdr.magic = NIPC_MAGIC_MSG;
1027
- resp_hdr.version = NIPC_VERSION;
1028
- resp_hdr.header_len = NIPC_HEADER_LEN;
1029
- resp_hdr.payload_len = (uint32_t)response_len;
1030
-
1031
- /* Use a stack buffer for small responses, heap for large ones */
1032
- uint8_t stack_msg[4096];
1033
- uint8_t *msg = (msg_len <= sizeof(stack_msg)) ? stack_msg :
1034
- service_malloc(msg_len, NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL);
1035
- if (!msg)
1036
- break;
1037
-
1038
- nipc_header_encode(&resp_hdr, msg, NIPC_HEADER_LEN);
1039
- if (response_len > 0)
1040
- memcpy(msg + NIPC_HEADER_LEN, resp_buf, response_len);
1041
-
1042
- nipc_shm_error_t serr = nipc_shm_send(shm, msg, msg_len);
1043
- if (msg != stack_msg)
1044
- free(msg);
1045
- if (serr != NIPC_SHM_OK)
1046
- break;
1047
- } else {
1048
- nipc_uds_error_t uerr = nipc_uds_send(
1049
- session, &resp_hdr, resp_buf, response_len);
1050
- if (uerr != NIPC_UDS_OK)
1051
- break;
1052
- }
1053
-
1054
- if (dispatch_err == NIPC_ERR_OVERFLOW)
1055
- break;
1056
- }
1057
-
1058
- free(recv_buf);
1059
-}
1060
-
1061
-/* ------------------------------------------------------------------ */
1062
-/* Internal: per-session handler thread */
1063
-/* ------------------------------------------------------------------ */
1064
-
1065
-/* Thread function: handles one client session from accept to disconnect. */
1066
-static void *session_handler_thread(void *arg)
1067
-{
1068
- nipc_session_ctx_t *sctx = (nipc_session_ctx_t *)arg;
1069
- nipc_managed_server_t *server = sctx->server;
1070
-
1071
- /* Allocate a per-session response buffer */
1072
- size_t resp_size = (size_t)sctx->session.max_response_payload_bytes;
1073
- if (resp_size < 1024u)
1074
- resp_size = 1024u;
1075
- uint8_t *resp_buf = service_malloc(
1076
- resp_size, NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL);
1077
- if (resp_buf) {
1078
- server_handle_session(server, &sctx->session, sctx->shm,
1079
- resp_buf, resp_size);
1080
- free(resp_buf);
1081
- }
1082
-
1083
- /* Cleanup SHM and session */
1084
- if (sctx->shm) {
1085
- nipc_shm_destroy(sctx->shm);
1086
- free(sctx->shm);
1087
- }
1088
- nipc_uds_close_session(&sctx->session);
1089
-
1090
- /* Mark inactive so the acceptor's reap loop (or server destroy)
1091
- * can join this thread and free sctx. Do NOT remove from the
1092
- * tracking array here — the reap/destroy path owns that. */
1093
- __atomic_store_n(&sctx->active, false, __ATOMIC_RELEASE);
1094
- return NULL;
1095
-}
1096
-
1097
-/* ------------------------------------------------------------------ */
1098
-/* Internal: reap finished session threads */
1099
-/* ------------------------------------------------------------------ */
1100
-
1101
-/* Reap all finished (inactive) session threads. Called with lock held. */
1102
-static void server_reap_sessions_locked(nipc_managed_server_t *server)
1103
-{
1104
- int i = 0;
1105
- while (i < server->session_count) {
1106
- nipc_session_ctx_t *s = server->sessions[i];
1107
- if (!__atomic_load_n(&s->active, __ATOMIC_ACQUIRE)) {
1108
- pthread_join(s->thread, NULL);
1109
- /* Swap with last, free */
1110
- server->sessions[i] = server->sessions[server->session_count - 1];
1111
- server->session_count--;
1112
- free(s);
1113
- } else {
1114
- i++;
1115
- }
1116
- }
1117
-
1118
-}
1119
-
1120
-static void server_destroy_precreated_shm(nipc_shm_ctx_t **shm)
1121
-{
1122
- if (!shm || !*shm)
1123
- return;
1124
- nipc_shm_destroy(*shm);
1125
- free(*shm);
1126
- *shm = NULL;
1127
-}
1128
-
1129
-static bool server_prepare_accept_config(nipc_managed_server_t *server,
1130
- uint64_t sid,
1131
- nipc_uds_server_config_t *cfg_out,
1132
- nipc_shm_ctx_t **shm_out)
1133
-{
1134
- *cfg_out = server->base_config;
1135
- cfg_out->max_request_payload_bytes =
1136
- __atomic_load_n(&server->learned_request_payload_bytes, __ATOMIC_ACQUIRE);
1137
- cfg_out->max_response_payload_bytes =
1138
- __atomic_load_n(&server->learned_response_payload_bytes, __ATOMIC_ACQUIRE);
1139
- *shm_out = NULL;
1140
-
1141
- uint32_t shm_profiles = cfg_out->supported_profiles &
1142
- (NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
1143
- if (shm_profiles == 0)
1144
- return true;
1145
-
1146
- uint32_t request_capacity;
1147
- uint32_t response_capacity;
1148
- if (!header_payload_len_u32(NIPC_MAX_PAYLOAD_CAP, &request_capacity) ||
1149
- !header_payload_len_u32(cfg_out->max_response_payload_bytes, &response_capacity)) {
1150
- cfg_out->supported_profiles &= ~shm_profiles;
1151
- cfg_out->preferred_profiles &= ~shm_profiles;
1152
- return cfg_out->supported_profiles != 0;
1153
- }
1154
-
1155
- nipc_shm_ctx_t *shm = service_calloc(
1156
- 1, sizeof(nipc_shm_ctx_t),
1157
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SHM_CTX_CALLOC_INTERNAL);
1158
- if (!shm)
1159
- return false;
1160
-
1161
- /* HELLO has not been read yet, so the request segment must cover any
1162
- * client proposal the handshake may legally echo back. */
1163
- nipc_shm_error_t serr = nipc_shm_server_create(
1164
- server->run_dir, server->service_name,
1165
- sid,
1166
- request_capacity,
1167
- response_capacity,
1168
- shm);
1169
- if (serr == NIPC_SHM_OK) {
1170
- *shm_out = shm;
1171
- return true;
1172
- }
1173
-
1174
- free(shm);
1175
- cfg_out->supported_profiles &= ~(NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
1176
- cfg_out->preferred_profiles &= ~(NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
1177
- return cfg_out->supported_profiles != 0;
1178
-}
1179
-
1180
-/* ------------------------------------------------------------------ */
1181
-/* Public API: managed server */
1182
-/* ------------------------------------------------------------------ */
1183
-
1184
-static nipc_error_t server_init_raw(nipc_managed_server_t *server,
1185
- const char *run_dir,
1186
- const char *service_name,
1187
- const nipc_uds_server_config_t *config,
1188
- int worker_count,
1189
- uint16_t expected_method_code,
1190
- nipc_server_handler_fn handler,
1191
- void *user)
1192
-{
1193
- if (!server)
1194
- return NIPC_ERR_BAD_LAYOUT;
1195
-
1196
- memset(server, 0, sizeof(*server));
1197
- server->listener.fd = -1;
1198
- __atomic_store_n(&server->running, false, __ATOMIC_RELAXED);
1199
- server->acceptor_started = false;
1200
-
1201
- if (!run_dir || !service_name || !config || !handler)
1202
- return NIPC_ERR_BAD_LAYOUT;
1203
-
1204
- if (worker_count < 1)
1205
- worker_count = 1;
1206
-
1207
- /* Store config */
1208
- {
1209
- size_t len = strlen(run_dir);
1210
- if (len >= sizeof(server->run_dir))
1211
- len = sizeof(server->run_dir) - 1;
1212
- memcpy(server->run_dir, run_dir, len);
1213
- server->run_dir[len] = '\0';
1214
- }
1215
- {
1216
- size_t len = strlen(service_name);
1217
- if (len >= sizeof(server->service_name))
1218
- len = sizeof(server->service_name) - 1;
1219
- memcpy(server->service_name, service_name, len);
1220
- server->service_name[len] = '\0';
1221
- }
1222
-
1223
- server->handler = handler;
1224
- server->handler_user = user;
1225
- server->worker_count = worker_count;
1226
- server->expected_method_code = expected_method_code;
1227
- server->base_config = *config;
1228
- server->learned_request_payload_bytes =
1229
- (config && config->max_request_payload_bytes > 0)
1230
- ? config->max_request_payload_bytes
1231
- : NIPC_MAX_PAYLOAD_DEFAULT;
1232
- server->learned_response_payload_bytes =
1233
- (config && config->max_response_payload_bytes > 0)
1234
- ? config->max_response_payload_bytes
1235
- : NIPC_MAX_PAYLOAD_DEFAULT;
1236
-
1237
- /* Initialize session tracking */
1238
- server->session_capacity = worker_count * 2; /* room for slots being reaped */
1239
- if (server->session_capacity < 16)
1240
- server->session_capacity = 16;
1241
- server->sessions = service_calloc((size_t)server->session_capacity,
1242
- sizeof(nipc_session_ctx_t *),
1243
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SESSIONS_CALLOC_INTERNAL);
1244
- if (!server->sessions)
1245
- return NIPC_ERR_OVERFLOW;
1246
- server->session_count = 0;
1247
- server->next_session_id = 1; /* spec: monotonic counter starting at 1 */
1248
- pthread_mutex_init(&server->sessions_lock, NULL);
1249
-
1250
- /* Clean up stale SHM regions from previous crashes (spec requirement:
1251
- * runs once at server startup, before the listener begins accepting). */
1252
- nipc_shm_cleanup_stale(run_dir, service_name);
1253
-
1254
- /* Start listening via L1 */
1255
- nipc_uds_error_t uerr = nipc_uds_listen(
1256
- run_dir, service_name, config, &server->listener);
1257
- if (uerr != NIPC_UDS_OK) {
1258
- free(server->sessions);
1259
- server->sessions = NULL;
1260
- pthread_mutex_destroy(&server->sessions_lock);
1261
- return NIPC_ERR_BAD_LAYOUT;
1262
- }
1263
-
1264
- return NIPC_OK;
1265
-}
1266
-
1267
-nipc_error_t nipc_server_init_typed(nipc_managed_server_t *server,
1268
- const char *run_dir,
1269
- const char *service_name,
1270
- const nipc_server_config_t *config,
1271
- int worker_count,
1272
- const nipc_cgroups_service_handler_t *service_handler)
1273
-{
1274
- if (!service_handler)
1275
- return NIPC_ERR_BAD_LAYOUT;
1276
-
1277
- nipc_uds_server_config_t typed_cfg = service_server_config_to_transport(config);
1278
- if (typed_cfg.max_request_payload_bytes == 0)
1279
- typed_cfg.max_request_payload_bytes = cgroups_request_payload_default();
1280
- if (typed_cfg.max_response_payload_bytes == 0)
1281
- typed_cfg.max_response_payload_bytes = cgroups_response_payload_default();
1282
-
1283
- nipc_error_t err = server_init_raw(server, run_dir, service_name,
1284
- &typed_cfg, worker_count,
1285
- NIPC_METHOD_CGROUPS_SNAPSHOT,
1286
- server_typed_dispatch, server);
1287
- if (err != NIPC_OK)
1288
- return err;
1289
-
1290
- server->service_handler = *service_handler;
1291
- return NIPC_OK;
1292
-}
1293
-
1294
-nipc_error_t nipc_server_init_raw_for_tests(nipc_managed_server_t *server,
1295
- const char *run_dir,
1296
- const char *service_name,
1297
- const nipc_uds_server_config_t *config,
1298
- int worker_count,
1299
- uint16_t expected_method_code,
1300
- nipc_server_handler_fn handler,
1301
- void *user)
1302
-{
1303
- return server_init_raw(server, run_dir, service_name, config,
1304
- worker_count, expected_method_code, handler, user);
1305
-}
1306
-
1307
-void nipc_server_run(nipc_managed_server_t *server)
1308
-{
1309
- __atomic_store_n(&server->running, true, __ATOMIC_RELEASE);
1310
-
1311
- while (__atomic_load_n(&server->running, __ATOMIC_RELAXED)) {
1312
- /* Poll the listener fd before blocking on accept */
1313
- int pr = poll_with_shutdown(server->listener.fd, &server->running);
1314
- if (pr <= 0)
1315
- break; /* shutdown or error */
1316
-
1317
- /* Accept one client via L1 */
1318
- nipc_uds_session_t session;
1319
- memset(&session, 0, sizeof(session));
1320
- session.fd = -1;
1321
-
1322
- uint64_t sid = server->next_session_id++;
1323
- nipc_uds_server_config_t accept_cfg;
1324
- nipc_shm_ctx_t *prepared_shm = NULL;
1325
- if (!server_prepare_accept_config(server, sid, &accept_cfg, &prepared_shm)) {
1326
- usleep(10000);
1327
- continue;
1328
- }
1329
-
1330
- server->listener.config = accept_cfg;
1331
- nipc_uds_error_t uerr = nipc_uds_accept(
1332
- &server->listener, sid, &session);
1333
- if (uerr != NIPC_UDS_OK) {
1334
- server_destroy_precreated_shm(&prepared_shm);
1335
- if (!__atomic_load_n(&server->running, __ATOMIC_RELAXED))
1336
- break;
1337
- usleep(10000);
1338
- continue;
1339
- }
1340
-
1341
- server_note_request_capacity(server, session.max_request_payload_bytes);
1342
- server_note_response_capacity(server, session.max_response_payload_bytes);
1343
-
1344
- /* Enforce worker_count limit: reap finished sessions, check count */
1345
- pthread_mutex_lock(&server->sessions_lock);
1346
- server_reap_sessions_locked(server);
1347
-
1348
- if (server->session_count >= server->worker_count) {
1349
- /* At capacity: reject this client by closing the session */
1350
- pthread_mutex_unlock(&server->sessions_lock);
1351
- server_destroy_precreated_shm(&prepared_shm);
1352
- nipc_uds_close_session(&session);
1353
- continue;
1354
- }
1355
-
1356
- /* SHM profile guarantee: only negotiate SHM for sessions that already
1357
- * have a prepared per-session SHM region. */
1358
- nipc_shm_ctx_t *shm = prepared_shm;
1359
- if (session.selected_profile == NIPC_PROFILE_SHM_HYBRID ||
1360
- session.selected_profile == NIPC_PROFILE_SHM_FUTEX) {
1361
- if (!shm) {
1362
- pthread_mutex_unlock(&server->sessions_lock);
1363
- nipc_uds_close_session(&session);
1364
- continue;
1365
- }
1366
- } else {
1367
- server_destroy_precreated_shm(&prepared_shm);
1368
- shm = NULL;
1369
- }
1370
-
1371
- /* Create session context */
1372
- nipc_session_ctx_t *sctx = service_calloc(
1373
- 1, sizeof(nipc_session_ctx_t),
1374
- NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SESSION_CTX_CALLOC_INTERNAL);
1375
- if (!sctx) {
1376
- if (shm) { nipc_shm_destroy(shm); free(shm); }
1377
- pthread_mutex_unlock(&server->sessions_lock);
1378
- nipc_uds_close_session(&session);
1379
- continue;
1380
- }
1381
-
1382
- sctx->server = server;
1383
- sctx->session = session;
1384
- sctx->shm = shm;
1385
- sctx->id = sid;
1386
- __atomic_store_n(&sctx->active, true, __ATOMIC_RELAXED);
1387
-
1388
- server->sessions[server->session_count++] = sctx;
1389
- pthread_mutex_unlock(&server->sessions_lock);
1390
-
1391
- /* Spawn handler thread for this session */
1392
- int rc = service_pthread_create(&sctx->thread, NULL,
1393
- session_handler_thread, sctx);
1394
- if (rc != 0) {
1395
- /* Thread creation failed: clean up */
1396
- pthread_mutex_lock(&server->sessions_lock);
1397
- /* Remove the sctx we just added */
1398
- for (int i = 0; i < server->session_count; i++) {
1399
- if (server->sessions[i] == sctx) {
1400
- server->sessions[i] = server->sessions[server->session_count - 1];
1401
- server->session_count--;
1402
- break;
1403
- }
1404
- }
1405
- pthread_mutex_unlock(&server->sessions_lock);
1406
-
1407
- if (shm) { nipc_shm_destroy(shm); free(shm); }
1408
- nipc_uds_close_session(&session);
1409
- free(sctx);
1410
- }
1411
- }
1412
-}
1413
-
1414
-void nipc_server_stop(nipc_managed_server_t *server)
1415
-{
1416
- __atomic_store_n(&server->running, false, __ATOMIC_RELEASE);
1417
-}
1418
-
1419
-bool nipc_server_drain(nipc_managed_server_t *server, uint32_t timeout_ms)
1420
-{
1421
- /* 1. Stop accepting new clients.
1422
- * Do NOT close the listener here — the run loop may still be
1423
- * polling on listener.fd. Setting the flag is enough; the run
1424
- * loop will exit on its next poll timeout (100ms). The listener
1425
- * is closed later by nipc_server_destroy(). */
1426
- __atomic_store_n(&server->running, false, __ATOMIC_RELEASE);
1427
-
1428
- /* 2. Wait for in-flight sessions to complete */
1429
- bool all_drained = true;
1430
- if (server->sessions) {
1431
- struct timespec deadline;
1432
- clock_gettime(CLOCK_MONOTONIC, &deadline);
1433
- deadline.tv_sec += timeout_ms / 1000;
1434
- deadline.tv_nsec += (timeout_ms % 1000) * 1000000L;
1435
- if (deadline.tv_nsec >= 1000000000L) {
1436
- deadline.tv_sec++;
1437
- deadline.tv_nsec -= 1000000000L;
1438
- }
1439
-
1440
- /* Poll until all sessions are inactive or timeout */
1441
- while (1) {
1442
- pthread_mutex_lock(&server->sessions_lock);
1443
- int active_count = 0;
1444
- for (int i = 0; i < server->session_count; i++) {
1445
- if (__atomic_load_n(&server->sessions[i]->active,
1446
- __ATOMIC_ACQUIRE))
1447
- active_count++;
1448
- }
1449
- pthread_mutex_unlock(&server->sessions_lock);
1450
-
1451
- if (active_count == 0)
1452
- break;
1453
-
1454
- struct timespec now;
1455
- clock_gettime(CLOCK_MONOTONIC, &now);
1456
- if (now.tv_sec > deadline.tv_sec ||
1457
- (now.tv_sec == deadline.tv_sec &&
1458
- now.tv_nsec >= deadline.tv_nsec)) {
1459
- /* Timeout: force-close session fds to unblock recv.
1460
- * Closing the fd causes poll/recv to return error,
1461
- * which terminates the session handler loop. */
1462
- pthread_mutex_lock(&server->sessions_lock);
1463
- for (int i = 0; i < server->session_count; i++) {
1464
- nipc_session_ctx_t *s = server->sessions[i];
1465
- if (__atomic_load_n(&s->active, __ATOMIC_ACQUIRE)) {
1466
- if (s->session.fd >= 0) {
1467
- shutdown(s->session.fd, SHUT_RDWR);
1468
- }
1469
- }
1470
- }
1471
- pthread_mutex_unlock(&server->sessions_lock);
1472
- all_drained = false;
1473
- break;
1474
- }
1475
-
1476
- usleep(5000); /* 5ms poll interval */
1477
- }
1478
-
1479
- /* 3. Join all session threads (finished or not) */
1480
- pthread_mutex_lock(&server->sessions_lock);
1481
- for (int i = 0; i < server->session_count; i++) {
1482
- nipc_session_ctx_t *s = server->sessions[i];
1483
- pthread_mutex_unlock(&server->sessions_lock);
1484
- pthread_join(s->thread, NULL);
1485
- free(s);
1486
- pthread_mutex_lock(&server->sessions_lock);
1487
- }
1488
- server->session_count = 0;
1489
- pthread_mutex_unlock(&server->sessions_lock);
1490
-
1491
- free(server->sessions);
1492
- server->sessions = NULL;
1493
- server->session_capacity = 0;
1494
- pthread_mutex_destroy(&server->sessions_lock);
1495
- }
1496
-
1497
- server->worker_count = 0;
1498
- return all_drained;
1499
-}
1500
-
1501
-void nipc_server_destroy(nipc_managed_server_t *server)
1502
-{
1503
- __atomic_store_n(&server->running, false, __ATOMIC_RELEASE);
1504
- nipc_uds_close_listener(&server->listener);
1505
-
1506
- /* Join all active session threads */
1507
- if (server->sessions) {
1508
- pthread_mutex_lock(&server->sessions_lock);
1509
- for (int i = 0; i < server->session_count; i++) {
1510
- nipc_session_ctx_t *s = server->sessions[i];
1511
- pthread_mutex_unlock(&server->sessions_lock);
1512
- pthread_join(s->thread, NULL);
1513
- free(s);
1514
- pthread_mutex_lock(&server->sessions_lock);
1515
- }
1516
- server->session_count = 0;
1517
- pthread_mutex_unlock(&server->sessions_lock);
1518
-
1519
- free(server->sessions);
1520
- server->sessions = NULL;
1521
- server->session_capacity = 0;
1522
- pthread_mutex_destroy(&server->sessions_lock);
1523
- }
1524
-
1525
- server->worker_count = 0;
1526
-}
1527
-
1528
-/* ------------------------------------------------------------------ */
1529
-/* L3: Client-side cgroups snapshot cache */
1530
-/* ------------------------------------------------------------------ */
1531
-
1532
-/* Free all owned strings in cache items and the items array itself. */
1533
-static void cache_free_items(nipc_cgroups_cache_item_t *items, uint32_t count)
1534
-{
1535
- if (!items)
1536
- return;
1537
-
1538
- for (uint32_t i = 0; i < count; i++) {
1539
- free(items[i].name);
1540
- free(items[i].path);
1541
- }
1542
- free(items);
1543
-}
1544
-
1545
-/* Hash a name string (djb2). Combined with item hash for bucket index. */
1546
-static uint32_t cache_hash_name(const char *name)
1547
-{
1548
- uint32_t h = 5381;
1549
- for (const unsigned char *p = (const unsigned char *)name; *p; p++)
1550
- h = ((h << 5) + h) + *p;
1551
- return h;
1552
-}
1553
-
1554
-/*
1555
- * Build the open-addressing hash table from the items array.
1556
- * Uses (item.hash ^ name_hash) as the probe key.
1557
- * Load factor <= 0.5 (bucket_count >= 2 * item_count).
1558
- */
1559
-static bool cache_build_hashtable(nipc_cgroups_cache_t *cache)
1560
-{
1561
- free(cache->buckets);
1562
- cache->buckets = NULL;
1563
- cache->bucket_count = 0;
1564
-
1565
- if (cache->item_count == 0)
1566
- return true;
1567
-
1568
- uint32_t bcount = next_power_of_2_u32(cache->item_count * 2);
1569
- nipc_cgroups_hash_bucket_t *buckets = service_calloc(bcount,
1570
- sizeof(nipc_cgroups_hash_bucket_t),
1571
- NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_BUCKETS_CALLOC_INTERNAL);
1572
- if (!buckets)
1573
- return false;
1574
-
1575
- uint32_t mask = bcount - 1;
1576
- for (uint32_t i = 0; i < cache->item_count; i++) {
1577
- uint32_t key = cache->items[i].hash ^ cache_hash_name(cache->items[i].name);
1578
- uint32_t slot = key & mask;
1579
-
1580
- /* Linear probe for an empty bucket */
1581
- while (buckets[slot].used)
1582
- slot = (slot + 1) & mask;
1583
-
1584
- buckets[slot].index = i;
1585
- buckets[slot].used = true;
1586
- }
1587
-
1588
- cache->buckets = buckets;
1589
- cache->bucket_count = bcount;
1590
- return true;
1591
-}
1592
-
1593
-/*
1594
- * Build a new cache from a decoded snapshot view. Copies all strings
1595
- * from the ephemeral view into owned heap allocations.
1596
- *
1597
- * Returns the new items array and sets *count_out. Returns NULL on
1598
- * allocation failure.
1599
- */
1600
-static nipc_cgroups_cache_item_t *cache_build_items(
1601
- const nipc_cgroups_resp_view_t *view,
1602
- uint32_t *count_out)
1603
-{
1604
- uint32_t n = view->item_count;
1605
- *count_out = 0;
1606
-
1607
- if (n == 0)
1608
- return NULL; /* empty snapshot is valid */
1609
-
1610
- nipc_cgroups_cache_item_t *items = service_calloc(
1611
- n, sizeof(nipc_cgroups_cache_item_t),
1612
- NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEMS_CALLOC_INTERNAL);
1613
- if (!items)
1614
- return NULL;
1615
-
1616
- for (uint32_t i = 0; i < n; i++) {
1617
- nipc_cgroups_item_view_t iv;
1618
- nipc_error_t err = nipc_cgroups_resp_item(view, i, &iv);
1619
- if (err != NIPC_OK) {
1620
- /* Malformed item: abort build, free partial */
1621
- cache_free_items(items, i);
1622
- return NULL;
1623
- }
1624
-
1625
- items[i].hash = iv.hash;
1626
- items[i].options = iv.options;
1627
- items[i].enabled = iv.enabled;
1628
-
1629
- /* Copy name (add NUL terminator) */
1630
- items[i].name = service_malloc(
1631
- iv.name.len + 1, NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEM_NAME_MALLOC_INTERNAL);
1632
- if (!items[i].name) {
1633
- cache_free_items(items, i);
1634
- return NULL;
1635
- }
1636
- if (iv.name.len > 0)
1637
- memcpy(items[i].name, iv.name.ptr, iv.name.len);
1638
- items[i].name[iv.name.len] = '\0';
1639
-
1640
- /* Copy path (add NUL terminator) */
1641
- items[i].path = service_malloc(
1642
- iv.path.len + 1, NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEM_PATH_MALLOC_INTERNAL);
1643
- if (!items[i].path) {
1644
- free(items[i].name);
1645
- cache_free_items(items, i);
1646
- return NULL;
1647
- }
1648
- if (iv.path.len > 0)
1649
- memcpy(items[i].path, iv.path.ptr, iv.path.len);
1650
- items[i].path[iv.path.len] = '\0';
1651
- }
1652
-
1653
- *count_out = n;
1654
- return items;
1655
-}
1656
-
1657
-void nipc_cgroups_cache_init(nipc_cgroups_cache_t *cache,
1658
- const char *run_dir,
1659
- const char *service_name,
1660
- const nipc_client_config_t *config)
1661
-{
1662
- memset(cache, 0, sizeof(*cache));
1663
-
1664
- nipc_client_init(&cache->client, run_dir, service_name, config);
1665
-
1666
- cache->items = NULL;
1667
- cache->item_count = 0;
1668
- cache->systemd_enabled = 0;
1669
- cache->generation = 0;
1670
- cache->populated = false;
1671
- cache->buckets = NULL;
1672
- cache->bucket_count = 0;
1673
- cache->refresh_success_count = 0;
1674
- cache->refresh_failure_count = 0;
1675
-
1676
- cache->response_buf = NULL;
1677
- cache->response_buf_size = 0;
1678
-}
1679
-
1680
-bool nipc_cgroups_cache_refresh(nipc_cgroups_cache_t *cache)
1681
-{
1682
- /* Drive L2 connection lifecycle */
1683
- nipc_client_refresh(&cache->client);
1684
-
1685
- /* Attempt snapshot call */
1686
- nipc_cgroups_resp_view_t view;
1687
- nipc_error_t err = nipc_client_call_cgroups_snapshot(&cache->client, &view);
1688
-
1689
- if (err != NIPC_OK) {
1690
- /* Refresh failed -- preserve previous cache */
1691
- cache->refresh_failure_count++;
1692
- return false;
1693
- }
1694
-
1695
- /* Build new cache from the snapshot view */
1696
- uint32_t new_count = 0;
1697
- nipc_cgroups_cache_item_t *new_items = NULL;
1698
-
1699
- if (view.item_count > 0) {
1700
- new_items = cache_build_items(&view, &new_count);
1701
- if (!new_items && view.item_count > 0) {
1702
- /* Build failed (allocation error) -- preserve old cache */
1703
- cache->refresh_failure_count++;
1704
- return false;
1705
- }
1706
- }
1707
-
1708
- /* Replace old cache with new one */
1709
- cache_free_items(cache->items, cache->item_count);
1710
- cache->items = new_items;
1711
- cache->item_count = new_count;
1712
- cache->systemd_enabled = view.systemd_enabled;
1713
- cache->generation = view.generation;
1714
- cache->populated = true;
1715
- cache->refresh_success_count++;
1716
-
1717
- /* Record monotonic timestamp */
1718
- struct timespec ts;
1719
- clock_gettime(CLOCK_MONOTONIC, &ts);
1720
- cache->last_refresh_ts = (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
1721
-
1722
- /* Rebuild hash table for O(1) lookup */
1723
- cache_build_hashtable(cache);
1724
-
1725
- return true;
1726
-}
1727
-
1728
-const nipc_cgroups_cache_item_t *nipc_cgroups_cache_lookup(
1729
- const nipc_cgroups_cache_t *cache,
1730
- uint32_t hash,
1731
- const char *name)
1732
-{
1733
- if (!cache->populated || !cache->items || !name)
1734
- return NULL;
1735
-
1736
- /* Use hash table if available, fall back to linear scan */
1737
- if (cache->buckets && cache->bucket_count > 0) {
1738
- uint32_t key = hash ^ cache_hash_name(name);
1739
- uint32_t mask = cache->bucket_count - 1;
1740
- uint32_t slot = key & mask;
1741
-
1742
- while (cache->buckets[slot].used) {
1743
- uint32_t idx = cache->buckets[slot].index;
1744
- if (cache->items[idx].hash == hash &&
1745
- strcmp(cache->items[idx].name, name) == 0) {
1746
- return &cache->items[idx];
1747
- }
1748
- slot = (slot + 1) & mask;
1749
- }
1750
- return NULL;
1751
- }
1752
-
1753
- /* Fallback linear scan (hash table allocation failed) */
1754
- for (uint32_t i = 0; i < cache->item_count; i++) {
1755
- if (cache->items[i].hash == hash &&
1756
- strcmp(cache->items[i].name, name) == 0) {
1757
- return &cache->items[i];
1758
- }
1759
- }
1760
-
1761
- return NULL;
1762
-}
1763
-
1764
-void nipc_cgroups_cache_status(const nipc_cgroups_cache_t *cache,
1765
- nipc_cgroups_cache_status_t *out)
1766
-{
1767
- out->populated = cache->populated;
1768
- out->item_count = cache->item_count;
1769
- out->systemd_enabled = cache->systemd_enabled;
1770
- out->generation = cache->generation;
1771
- out->refresh_success_count = cache->refresh_success_count;
1772
- out->refresh_failure_count = cache->refresh_failure_count;
1773
- out->connection_state = cache->client.state;
1774
- out->last_refresh_ts = cache->last_refresh_ts;
1775
-}
1776
-
1777
-void nipc_cgroups_cache_close(nipc_cgroups_cache_t *cache)
1778
-{
1779
- cache_free_items(cache->items, cache->item_count);
1780
- cache->items = NULL;
1781
- cache->item_count = 0;
1782
- cache->populated = false;
1783
-
1784
- free(cache->buckets);
1785
- cache->buckets = NULL;
1786
- cache->bucket_count = 0;
1787
-
1788
- free(cache->response_buf);
1789
- cache->response_buf = NULL;
1790
- cache->response_buf_size = 0;
1791
-
1792
- nipc_client_close(&cache->client);
1793
-}
166
+/* Managed server implementation lives in netipc_service_posix_server.c. */
src/libnetdata/netipc/src/service/netipc_service_apps_lookup.c
new
+141
@@ -0,0 +1,141 @@
1
+#include "netipc_service_platform.h"
2
+
3
+#include "netipc/netipc_protocol.h"
4
+
5
+static bool apps_lookup_request_size(uint32_t pid_count, size_t *size_out)
6
+{
7
+ if (nipc_service_common_mul_would_overflow(
8
+ (size_t)pid_count, NIPC_LOOKUP_DIR_ENTRY_SIZE) ||
9
+ nipc_service_common_mul_would_overflow(
10
+ (size_t)pid_count, NIPC_APPS_LOOKUP_KEY_SIZE))
11
+ return false;
12
+
13
+ size_t dir = (size_t)pid_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
14
+ size_t keys = (size_t)pid_count * NIPC_APPS_LOOKUP_KEY_SIZE;
15
+#if SIZE_MAX <= UINT32_MAX
16
+ if (NIPC_APPS_LOOKUP_REQ_HDR_SIZE > SIZE_MAX - dir ||
17
+ NIPC_APPS_LOOKUP_REQ_HDR_SIZE + dir > SIZE_MAX - keys)
18
+ return false;
19
+#endif
20
+ *size_out = NIPC_APPS_LOOKUP_REQ_HDR_SIZE + dir + keys;
21
+ return true;
22
+}
23
+
24
+static nipc_error_t apps_lookup_dispatch(void *user,
25
+ const nipc_header_t *request_hdr,
26
+ const uint8_t *request_payload,
27
+ size_t request_len,
28
+ uint8_t *response_buf,
29
+ size_t response_buf_size,
30
+ size_t *response_len_out)
31
+{
32
+ nipc_apps_lookup_service_handler_t *service_handler =
33
+ (nipc_apps_lookup_service_handler_t *)user;
34
+ (void)request_hdr;
35
+
36
+ if (!service_handler->handle)
37
+ return NIPC_ERR_HANDLER_FAILED;
38
+
39
+ return nipc_dispatch_apps_lookup(
40
+ request_payload, request_len,
41
+ response_buf, response_buf_size, response_len_out,
42
+ service_handler->handle, service_handler->user);
43
+}
44
+
45
+typedef struct {
46
+ const uint32_t *pids;
47
+ uint32_t pid_count;
48
+ nipc_apps_lookup_resp_view_t *view_out;
49
+} apps_lookup_call_state_t;
50
+
51
+static nipc_error_t do_apps_lookup_attempt(nipc_client_ctx_t *ctx, void *state)
52
+{
53
+ apps_lookup_call_state_t *s = (apps_lookup_call_state_t *)state;
54
+
55
+ size_t req_size;
56
+ if (!apps_lookup_request_size(s->pid_count, &req_size))
57
+ return NIPC_ERR_OVERFLOW;
58
+ if (req_size > UINT32_MAX)
59
+ return NIPC_ERR_OVERFLOW;
60
+ if (ctx->session.max_request_payload_bytes > 0 &&
61
+ req_size > ctx->session.max_request_payload_bytes) {
62
+ nipc_service_common_client_note_request_capacity(ctx, (uint32_t)req_size);
63
+ return NIPC_ERR_OVERFLOW;
64
+ }
65
+ if (!nipc_service_platform_ensure_client_send_buffer(ctx, req_size))
66
+ return NIPC_ERR_OVERFLOW;
67
+
68
+ size_t req_len = nipc_apps_lookup_req_encode(
69
+ s->pids, s->pid_count, ctx->send_buf, ctx->send_buf_size);
70
+ if (req_len == 0)
71
+ return NIPC_ERR_BAD_LAYOUT;
72
+
73
+ const void *payload;
74
+ size_t payload_len;
75
+ nipc_error_t err = nipc_service_platform_do_raw_call(
76
+ ctx, NIPC_METHOD_APPS_LOOKUP, ctx->send_buf, req_len,
77
+ &payload, &payload_len);
78
+ if (err != NIPC_OK)
79
+ return err;
80
+
81
+ err = nipc_apps_lookup_resp_decode(payload, payload_len, s->view_out);
82
+ if (err != NIPC_OK)
83
+ return err;
84
+ if (s->view_out->item_count != s->pid_count)
85
+ return NIPC_ERR_BAD_ITEM_COUNT;
86
+ for (uint32_t i = 0; i < s->pid_count; i++) {
87
+ nipc_apps_lookup_item_view_t item;
88
+ err = nipc_apps_lookup_resp_item(s->view_out, i, &item);
89
+ if (err != NIPC_OK)
90
+ return err;
91
+ if (item.pid != s->pids[i])
92
+ return NIPC_ERR_BAD_LAYOUT;
93
+ }
94
+ return NIPC_OK;
95
+}
96
+
97
+nipc_error_t nipc_client_call_apps_lookup(
98
+ nipc_client_ctx_t *ctx,
99
+ const uint32_t *pids,
100
+ uint32_t pid_count,
101
+ nipc_apps_lookup_resp_view_t *view_out)
102
+{
103
+ apps_lookup_call_state_t state = {
104
+ .pids = pids,
105
+ .pid_count = pid_count,
106
+ .view_out = view_out,
107
+ };
108
+ return nipc_service_platform_call_with_retry(
109
+ ctx, do_apps_lookup_attempt, &state);
110
+}
111
+
112
+nipc_error_t nipc_server_init_apps_lookup(
113
+ nipc_managed_server_t *server,
114
+ const char *run_dir,
115
+ const char *service_name,
116
+ const nipc_server_config_t *config,
117
+ int worker_count,
118
+ const nipc_apps_lookup_service_handler_t *service_handler)
119
+{
120
+ if (!service_handler)
121
+ return NIPC_ERR_BAD_LAYOUT;
122
+
123
+ nipc_service_platform_server_config_t typed_cfg;
124
+ nipc_service_platform_server_config_from_service(&typed_cfg, config);
125
+ if (typed_cfg.max_request_payload_bytes == 0)
126
+ typed_cfg.max_request_payload_bytes =
127
+ nipc_service_common_response_payload_default();
128
+ if (typed_cfg.max_response_payload_bytes == 0)
129
+ typed_cfg.max_response_payload_bytes =
130
+ nipc_service_common_response_payload_default();
131
+
132
+ nipc_error_t err = nipc_service_platform_server_init_raw(
133
+ server, run_dir, service_name, &typed_cfg, worker_count,
134
+ NIPC_METHOD_APPS_LOOKUP, apps_lookup_dispatch,
135
+ &server->typed_handler.apps_lookup);
136
+ if (err != NIPC_OK)
137
+ return err;
138
+
139
+ server->typed_handler.apps_lookup = *service_handler;
140
+ return NIPC_OK;
141
+}
src/libnetdata/netipc/src/service/netipc_service_cgroups_cache.c
new
+48
@@ -0,0 +1,48 @@
1
+#include "netipc_service_cgroups_cache_common.h"
2
+#include "netipc_service_platform.h"
3
+
4
+static const nipc_service_common_cache_ops_t cgroups_cache_ops = {
5
+ .malloc_fn = nipc_service_platform_malloc,
6
+ .calloc_fn = nipc_service_platform_calloc,
7
+ .monotonic_ms_fn = nipc_service_platform_monotonic_ms,
8
+ .cache_buckets_fault_site =
9
+ NIPC_SERVICE_PLATFORM_TEST_FAULT_CACHE_BUCKETS_CALLOC_INTERNAL,
10
+ .cache_items_fault_site =
11
+ NIPC_SERVICE_PLATFORM_TEST_FAULT_CACHE_ITEMS_CALLOC_INTERNAL,
12
+ .cache_item_name_fault_site =
13
+ NIPC_SERVICE_PLATFORM_TEST_FAULT_CACHE_ITEM_NAME_MALLOC_INTERNAL,
14
+ .cache_item_path_fault_site =
15
+ NIPC_SERVICE_PLATFORM_TEST_FAULT_CACHE_ITEM_PATH_MALLOC_INTERNAL,
16
+};
17
+
18
+void nipc_cgroups_cache_init(nipc_cgroups_cache_t *cache,
19
+ const char *run_dir,
20
+ const char *service_name,
21
+ const nipc_client_config_t *config)
22
+{
23
+ nipc_service_common_cgroups_cache_init(cache, run_dir, service_name, config);
24
+}
25
+
26
+bool nipc_cgroups_cache_refresh(nipc_cgroups_cache_t *cache)
27
+{
28
+ return nipc_service_common_cgroups_cache_refresh(cache, &cgroups_cache_ops);
29
+}
30
+
31
+const nipc_cgroups_cache_item_t *nipc_cgroups_cache_lookup(
32
+ const nipc_cgroups_cache_t *cache,
33
+ uint32_t hash,
34
+ const char *name)
35
+{
36
+ return nipc_service_common_cgroups_cache_lookup(cache, hash, name);
37
+}
38
+
39
+void nipc_cgroups_cache_status(const nipc_cgroups_cache_t *cache,
40
+ nipc_cgroups_cache_status_t *out)
41
+{
42
+ nipc_service_common_cgroups_cache_status(cache, out);
43
+}
44
+
45
+void nipc_cgroups_cache_close(nipc_cgroups_cache_t *cache)
46
+{
47
+ nipc_service_common_cgroups_cache_close(cache);
48
+}
src/libnetdata/netipc/src/service/netipc_service_cgroups_cache_common.c
new
+223
@@ -0,0 +1,223 @@
1
+#include "netipc_service_cgroups_cache_common.h"
2
+
3
+#include "netipc/netipc_protocol.h"
4
+
5
+#include <stdlib.h>
6
+#include <string.h>
7
+
8
+static void cache_free_items(nipc_cgroups_cache_item_t *items, uint32_t count)
9
+{
10
+ if (!items)
11
+ return;
12
+
13
+ for (uint32_t i = 0; i < count; i++) {
14
+ free(items[i].name);
15
+ free(items[i].path);
16
+ }
17
+ free(items);
18
+}
19
+
20
+static uint32_t cache_hash_name(const char *name)
21
+{
22
+ uint32_t h = 5381;
23
+ for (const unsigned char *p = (const unsigned char *)name; *p; p++)
24
+ h = ((h << 5) + h) + *p;
25
+ return h;
26
+}
27
+
28
+static bool cache_build_hashtable(nipc_cgroups_cache_t *cache,
29
+ const nipc_service_common_cache_ops_t *ops)
30
+{
31
+ free(cache->buckets);
32
+ cache->buckets = NULL;
33
+ cache->bucket_count = 0;
34
+
35
+ if (cache->item_count == 0)
36
+ return true;
37
+
38
+ uint32_t bcount = nipc_service_common_next_power_of_2_u32(cache->item_count * 2);
39
+ nipc_cgroups_hash_bucket_t *buckets = ops->calloc_fn(
40
+ bcount, sizeof(nipc_cgroups_hash_bucket_t),
41
+ ops->cache_buckets_fault_site);
42
+ if (!buckets)
43
+ return false;
44
+
45
+ uint32_t mask = bcount - 1;
46
+ for (uint32_t i = 0; i < cache->item_count; i++) {
47
+ uint32_t key = cache->items[i].hash ^ cache_hash_name(cache->items[i].name);
48
+ uint32_t slot = key & mask;
49
+
50
+ while (buckets[slot].used)
51
+ slot = (slot + 1) & mask;
52
+
53
+ buckets[slot].index = i;
54
+ buckets[slot].used = true;
55
+ }
56
+
57
+ cache->buckets = buckets;
58
+ cache->bucket_count = bcount;
59
+ return true;
60
+}
61
+
62
+static nipc_cgroups_cache_item_t *cache_build_items(
63
+ const nipc_cgroups_resp_view_t *view,
64
+ const nipc_service_common_cache_ops_t *ops,
65
+ uint32_t *count_out)
66
+{
67
+ uint32_t n = view->item_count;
68
+ *count_out = 0;
69
+
70
+ if (n == 0)
71
+ return NULL;
72
+
73
+ nipc_cgroups_cache_item_t *items = ops->calloc_fn(
74
+ n, sizeof(nipc_cgroups_cache_item_t),
75
+ ops->cache_items_fault_site);
76
+ if (!items)
77
+ return NULL;
78
+
79
+ for (uint32_t i = 0; i < n; i++) {
80
+ nipc_cgroups_item_view_t iv;
81
+ nipc_error_t err = nipc_cgroups_resp_item(view, i, &iv);
82
+ if (err != NIPC_OK) {
83
+ cache_free_items(items, i);
84
+ return NULL;
85
+ }
86
+
87
+ items[i].hash = iv.hash;
88
+ items[i].options = iv.options;
89
+ items[i].enabled = iv.enabled;
90
+
91
+ items[i].name = ops->malloc_fn(
92
+ iv.name.len + 1, ops->cache_item_name_fault_site);
93
+ if (!items[i].name) {
94
+ cache_free_items(items, i);
95
+ return NULL;
96
+ }
97
+ if (iv.name.len > 0)
98
+ memcpy(items[i].name, iv.name.ptr, iv.name.len);
99
+ items[i].name[iv.name.len] = '\0';
100
+
101
+ items[i].path = ops->malloc_fn(
102
+ iv.path.len + 1, ops->cache_item_path_fault_site);
103
+ if (!items[i].path) {
104
+ free(items[i].name);
105
+ cache_free_items(items, i);
106
+ return NULL;
107
+ }
108
+ if (iv.path.len > 0)
109
+ memcpy(items[i].path, iv.path.ptr, iv.path.len);
110
+ items[i].path[iv.path.len] = '\0';
111
+ }
112
+
113
+ *count_out = n;
114
+ return items;
115
+}
116
+
117
+void nipc_service_common_cgroups_cache_init(nipc_cgroups_cache_t *cache,
118
+ const char *run_dir,
119
+ const char *service_name,
120
+ const nipc_client_config_t *config)
121
+{
122
+ memset(cache, 0, sizeof(*cache));
123
+ nipc_client_init(&cache->client, run_dir, service_name, config);
124
+}
125
+
126
+bool nipc_service_common_cgroups_cache_refresh(
127
+ nipc_cgroups_cache_t *cache,
128
+ const nipc_service_common_cache_ops_t *ops)
129
+{
130
+ nipc_client_refresh(&cache->client);
131
+
132
+ nipc_cgroups_resp_view_t view;
133
+ nipc_error_t err = nipc_client_call_cgroups_snapshot(&cache->client, &view);
134
+ if (err != NIPC_OK) {
135
+ cache->refresh_failure_count++;
136
+ return false;
137
+ }
138
+
139
+ uint32_t new_count = 0;
140
+ nipc_cgroups_cache_item_t *new_items = NULL;
141
+ if (view.item_count > 0) {
142
+ new_items = cache_build_items(&view, ops, &new_count);
143
+ if (!new_items) {
144
+ cache->refresh_failure_count++;
145
+ return false;
146
+ }
147
+ }
148
+
149
+ cache_free_items(cache->items, cache->item_count);
150
+ cache->items = new_items;
151
+ cache->item_count = new_count;
152
+ cache->systemd_enabled = view.systemd_enabled;
153
+ cache->generation = view.generation;
154
+ cache->populated = true;
155
+ cache->refresh_success_count++;
156
+ cache->last_refresh_ts = ops->monotonic_ms_fn();
157
+ cache_build_hashtable(cache, ops);
158
+
159
+ return true;
160
+}
161
+
162
+const nipc_cgroups_cache_item_t *nipc_service_common_cgroups_cache_lookup(
163
+ const nipc_cgroups_cache_t *cache,
164
+ uint32_t hash,
165
+ const char *name)
166
+{
167
+ if (!cache->populated || !cache->items || !name)
168
+ return NULL;
169
+
170
+ if (cache->buckets && cache->bucket_count > 0) {
171
+ uint32_t key = hash ^ cache_hash_name(name);
172
+ uint32_t mask = cache->bucket_count - 1;
173
+ uint32_t slot = key & mask;
174
+
175
+ while (cache->buckets[slot].used) {
176
+ uint32_t idx = cache->buckets[slot].index;
177
+ if (cache->items[idx].hash == hash &&
178
+ strcmp(cache->items[idx].name, name) == 0)
179
+ return &cache->items[idx];
180
+ slot = (slot + 1) & mask;
181
+ }
182
+ return NULL;
183
+ }
184
+
185
+ for (uint32_t i = 0; i < cache->item_count; i++) {
186
+ if (cache->items[i].hash == hash &&
187
+ strcmp(cache->items[i].name, name) == 0)
188
+ return &cache->items[i];
189
+ }
190
+
191
+ return NULL;
192
+}
193
+
194
+void nipc_service_common_cgroups_cache_status(const nipc_cgroups_cache_t *cache,
195
+ nipc_cgroups_cache_status_t *out)
196
+{
197
+ out->populated = cache->populated;
198
+ out->item_count = cache->item_count;
199
+ out->systemd_enabled = cache->systemd_enabled;
200
+ out->generation = cache->generation;
201
+ out->refresh_success_count = cache->refresh_success_count;
202
+ out->refresh_failure_count = cache->refresh_failure_count;
203
+ out->connection_state = cache->client.state;
204
+ out->last_refresh_ts = cache->last_refresh_ts;
205
+}
206
+
207
+void nipc_service_common_cgroups_cache_close(nipc_cgroups_cache_t *cache)
208
+{
209
+ cache_free_items(cache->items, cache->item_count);
210
+ cache->items = NULL;
211
+ cache->item_count = 0;
212
+ cache->populated = false;
213
+
214
+ free(cache->buckets);
215
+ cache->buckets = NULL;
216
+ cache->bucket_count = 0;
217
+
218
+ free(cache->response_buf);
219
+ cache->response_buf = NULL;
220
+ cache->response_buf_size = 0;
221
+
222
+ nipc_client_close(&cache->client);
223
+}
src/libnetdata/netipc/src/service/netipc_service_cgroups_cache_common.h
new
+29
@@ -0,0 +1,29 @@
1
+#ifndef NETIPC_SERVICE_CGROUPS_CACHE_COMMON_H
2
+#define NETIPC_SERVICE_CGROUPS_CACHE_COMMON_H
3
+
4
+#include "netipc_service_common.h"
5
+
6
+#ifdef __cplusplus
7
+extern "C" {
8
+#endif
9
+
10
+void nipc_service_common_cgroups_cache_init(nipc_cgroups_cache_t *cache,
11
+ const char *run_dir,
12
+ const char *service_name,
13
+ const nipc_client_config_t *config);
14
+bool nipc_service_common_cgroups_cache_refresh(
15
+ nipc_cgroups_cache_t *cache,
16
+ const nipc_service_common_cache_ops_t *ops);
17
+const nipc_cgroups_cache_item_t *nipc_service_common_cgroups_cache_lookup(
18
+ const nipc_cgroups_cache_t *cache,
19
+ uint32_t hash,
20
+ const char *name);
21
+void nipc_service_common_cgroups_cache_status(const nipc_cgroups_cache_t *cache,
22
+ nipc_cgroups_cache_status_t *out);
23
+void nipc_service_common_cgroups_cache_close(nipc_cgroups_cache_t *cache);
24
+
25
+#ifdef __cplusplus
26
+}
27
+#endif
28
+
29
+#endif /* NETIPC_SERVICE_CGROUPS_CACHE_COMMON_H */
src/libnetdata/netipc/src/service/netipc_service_cgroups_lookup.c
new
+148
@@ -0,0 +1,148 @@
1
+#include "netipc_service_platform.h"
2
+
3
+#include "netipc/netipc_protocol.h"
4
+
5
+#include <string.h>
6
+
7
+static bool cgroups_lookup_request_size(const nipc_str_view_t *paths,
8
+ uint32_t path_count,
9
+ size_t *size_out)
10
+{
11
+ if (nipc_service_common_mul_would_overflow(
12
+ (size_t)path_count, NIPC_LOOKUP_DIR_ENTRY_SIZE))
13
+ return false;
14
+
15
+ size_t data = NIPC_CGROUPS_LOOKUP_REQ_HDR_SIZE +
16
+ (size_t)path_count * NIPC_LOOKUP_DIR_ENTRY_SIZE;
17
+ for (uint32_t i = 0; i < path_count; i++) {
18
+ size_t aligned = nipc_align8(data);
19
+ if (aligned < data)
20
+ return false;
21
+ if (!paths || paths[i].len > SIZE_MAX - aligned - 1u)
22
+ return false;
23
+ data = aligned + (size_t)paths[i].len + 1u;
24
+ }
25
+ *size_out = data;
26
+ return true;
27
+}
28
+
29
+static nipc_error_t cgroups_lookup_dispatch(void *user,
30
+ const nipc_header_t *request_hdr,
31
+ const uint8_t *request_payload,
32
+ size_t request_len,
33
+ uint8_t *response_buf,
34
+ size_t response_buf_size,
35
+ size_t *response_len_out)
36
+{
37
+ nipc_cgroups_lookup_service_handler_t *service_handler =
38
+ (nipc_cgroups_lookup_service_handler_t *)user;
39
+ (void)request_hdr;
40
+
41
+ if (!service_handler->handle)
42
+ return NIPC_ERR_HANDLER_FAILED;
43
+
44
+ return nipc_dispatch_cgroups_lookup(
45
+ request_payload, request_len,
46
+ response_buf, response_buf_size, response_len_out,
47
+ service_handler->handle, service_handler->user);
48
+}
49
+
50
+typedef struct {
51
+ const nipc_str_view_t *paths;
52
+ uint32_t path_count;
53
+ nipc_cgroups_lookup_resp_view_t *view_out;
54
+} cgroups_lookup_call_state_t;
55
+
56
+static nipc_error_t do_cgroups_lookup_attempt(nipc_client_ctx_t *ctx,
57
+ void *state)
58
+{
59
+ cgroups_lookup_call_state_t *s = (cgroups_lookup_call_state_t *)state;
60
+
61
+ size_t req_size;
62
+ if (!cgroups_lookup_request_size(s->paths, s->path_count, &req_size))
63
+ return NIPC_ERR_OVERFLOW;
64
+ if (req_size > UINT32_MAX)
65
+ return NIPC_ERR_OVERFLOW;
66
+ if (ctx->session.max_request_payload_bytes > 0 &&
67
+ req_size > ctx->session.max_request_payload_bytes) {
68
+ nipc_service_common_client_note_request_capacity(ctx, (uint32_t)req_size);
69
+ return NIPC_ERR_OVERFLOW;
70
+ }
71
+ if (!nipc_service_platform_ensure_client_send_buffer(ctx, req_size))
72
+ return NIPC_ERR_OVERFLOW;
73
+
74
+ size_t req_len = nipc_cgroups_lookup_req_encode(
75
+ s->paths, s->path_count, ctx->send_buf, ctx->send_buf_size);
76
+ if (req_len == 0)
77
+ return NIPC_ERR_BAD_LAYOUT;
78
+
79
+ const void *payload;
80
+ size_t payload_len;
81
+ nipc_error_t err = nipc_service_platform_do_raw_call(
82
+ ctx, NIPC_METHOD_CGROUPS_LOOKUP, ctx->send_buf, req_len,
83
+ &payload, &payload_len);
84
+ if (err != NIPC_OK)
85
+ return err;
86
+
87
+ err = nipc_cgroups_lookup_resp_decode(payload, payload_len, s->view_out);
88
+ if (err != NIPC_OK)
89
+ return err;
90
+ if (s->view_out->item_count != s->path_count)
91
+ return NIPC_ERR_BAD_ITEM_COUNT;
92
+ for (uint32_t i = 0; i < s->path_count; i++) {
93
+ nipc_cgroups_lookup_item_view_t item;
94
+ err = nipc_cgroups_lookup_resp_item(s->view_out, i, &item);
95
+ if (err != NIPC_OK)
96
+ return err;
97
+ if (item.path.len != s->paths[i].len ||
98
+ memcmp(item.path.ptr, s->paths[i].ptr, item.path.len) != 0)
99
+ return NIPC_ERR_BAD_LAYOUT;
100
+ }
101
+ return NIPC_OK;
102
+}
103
+
104
+nipc_error_t nipc_client_call_cgroups_lookup(
105
+ nipc_client_ctx_t *ctx,
106
+ const nipc_str_view_t *paths,
107
+ uint32_t path_count,
108
+ nipc_cgroups_lookup_resp_view_t *view_out)
109
+{
110
+ cgroups_lookup_call_state_t state = {
111
+ .paths = paths,
112
+ .path_count = path_count,
113
+ .view_out = view_out,
114
+ };
115
+ return nipc_service_platform_call_with_retry(
116
+ ctx, do_cgroups_lookup_attempt, &state);
117
+}
118
+
119
+nipc_error_t nipc_server_init_cgroups_lookup(
120
+ nipc_managed_server_t *server,
121
+ const char *run_dir,
122
+ const char *service_name,
123
+ const nipc_server_config_t *config,
124
+ int worker_count,
125
+ const nipc_cgroups_lookup_service_handler_t *service_handler)
126
+{
127
+ if (!service_handler)
128
+ return NIPC_ERR_BAD_LAYOUT;
129
+
130
+ nipc_service_platform_server_config_t typed_cfg;
131
+ nipc_service_platform_server_config_from_service(&typed_cfg, config);
132
+ if (typed_cfg.max_request_payload_bytes == 0)
133
+ typed_cfg.max_request_payload_bytes =
134
+ nipc_service_common_response_payload_default();
135
+ if (typed_cfg.max_response_payload_bytes == 0)
136
+ typed_cfg.max_response_payload_bytes =
137
+ nipc_service_common_response_payload_default();
138
+
139
+ nipc_error_t err = nipc_service_platform_server_init_raw(
140
+ server, run_dir, service_name, &typed_cfg, worker_count,
141
+ NIPC_METHOD_CGROUPS_LOOKUP, cgroups_lookup_dispatch,
142
+ &server->typed_handler.cgroups_lookup);
143
+ if (err != NIPC_OK)
144
+ return err;
145
+
146
+ server->typed_handler.cgroups_lookup = *service_handler;
147
+ return NIPC_OK;
148
+}
src/libnetdata/netipc/src/service/netipc_service_cgroups_snapshot.c
new
+102
@@ -0,0 +1,102 @@
1
+#include "netipc_service_platform.h"
2
+
3
+#include "netipc/netipc_protocol.h"
4
+
5
+static uint32_t snapshot_max_items(
6
+ size_t response_buf_size,
7
+ const nipc_cgroups_service_handler_t *service_handler)
8
+{
9
+ if (service_handler->snapshot_max_items != 0)
10
+ return service_handler->snapshot_max_items;
11
+ return nipc_cgroups_builder_estimate_max_items(response_buf_size);
12
+}
13
+
14
+static nipc_error_t cgroups_snapshot_dispatch(void *user,
15
+ const nipc_header_t *request_hdr,
16
+ const uint8_t *request_payload,
17
+ size_t request_len,
18
+ uint8_t *response_buf,
19
+ size_t response_buf_size,
20
+ size_t *response_len_out)
21
+{
22
+ nipc_cgroups_service_handler_t *service_handler =
23
+ (nipc_cgroups_service_handler_t *)user;
24
+ (void)request_hdr;
25
+
26
+ if (!service_handler->handle)
27
+ return NIPC_ERR_HANDLER_FAILED;
28
+
29
+ return nipc_dispatch_cgroups_snapshot(
30
+ request_payload, request_len,
31
+ response_buf, response_buf_size, response_len_out,
32
+ snapshot_max_items(response_buf_size, service_handler),
33
+ service_handler->handle, service_handler->user);
34
+}
35
+
36
+typedef struct {
37
+ nipc_cgroups_resp_view_t *view_out;
38
+} cgroups_snapshot_call_state_t;
39
+
40
+static nipc_error_t do_cgroups_snapshot_attempt(nipc_client_ctx_t *ctx,
41
+ void *state)
42
+{
43
+ cgroups_snapshot_call_state_t *s = (cgroups_snapshot_call_state_t *)state;
44
+
45
+ nipc_cgroups_req_t req = { .layout_version = 1, .flags = 0 };
46
+ uint8_t req_buf[4];
47
+ size_t req_len = nipc_cgroups_req_encode(&req, req_buf, sizeof(req_buf));
48
+ if (req_len == 0)
49
+ return NIPC_ERR_TRUNCATED;
50
+
51
+ const void *payload;
52
+ size_t payload_len;
53
+ nipc_error_t err = nipc_service_platform_do_raw_call(
54
+ ctx, NIPC_METHOD_CGROUPS_SNAPSHOT, req_buf, req_len,
55
+ &payload, &payload_len);
56
+ if (err != NIPC_OK)
57
+ return err;
58
+
59
+ return nipc_cgroups_resp_decode(payload, payload_len, s->view_out);
60
+}
61
+
62
+nipc_error_t nipc_client_call_cgroups_snapshot(
63
+ nipc_client_ctx_t *ctx,
64
+ nipc_cgroups_resp_view_t *view_out)
65
+{
66
+ cgroups_snapshot_call_state_t state = {
67
+ .view_out = view_out,
68
+ };
69
+ return nipc_service_platform_call_with_retry(
70
+ ctx, do_cgroups_snapshot_attempt, &state);
71
+}
72
+
73
+nipc_error_t nipc_server_init_typed(
74
+ nipc_managed_server_t *server,
75
+ const char *run_dir,
76
+ const char *service_name,
77
+ const nipc_server_config_t *config,
78
+ int worker_count,
79
+ const nipc_cgroups_service_handler_t *service_handler)
80
+{
81
+ if (!service_handler)
82
+ return NIPC_ERR_BAD_LAYOUT;
83
+
84
+ nipc_service_platform_server_config_t typed_cfg;
85
+ nipc_service_platform_server_config_from_service(&typed_cfg, config);
86
+ if (typed_cfg.max_request_payload_bytes == 0)
87
+ typed_cfg.max_request_payload_bytes =
88
+ nipc_service_common_request_payload_default();
89
+ if (typed_cfg.max_response_payload_bytes == 0)
90
+ typed_cfg.max_response_payload_bytes =
91
+ nipc_service_common_response_payload_default();
92
+
93
+ nipc_error_t err = nipc_service_platform_server_init_raw(
94
+ server, run_dir, service_name, &typed_cfg, worker_count,
95
+ NIPC_METHOD_CGROUPS_SNAPSHOT, cgroups_snapshot_dispatch,
96
+ &server->typed_handler.cgroups_snapshot);
97
+ if (err != NIPC_OK)
98
+ return err;
99
+
100
+ server->typed_handler.cgroups_snapshot = *service_handler;
101
+ return NIPC_OK;
102
+}
src/libnetdata/netipc/src/service/netipc_service_common.c
new
+576
@@ -0,0 +1,576 @@
1
+#include "netipc_service_common.h"
2
+
3
+#include "netipc/netipc_protocol.h"
4
+
5
+#include <stdlib.h>
6
+#include <string.h>
7
+
8
+#define NIPC_SERVICE_RESPONSE_BUF_DEFAULT 65536u
9
+
10
+uint32_t nipc_service_common_next_power_of_2_u32(uint32_t n)
11
+{
12
+ if (n < 16)
13
+ return 16;
14
+ n--;
15
+ n |= n >> 1;
16
+ n |= n >> 2;
17
+ n |= n >> 4;
18
+ n |= n >> 8;
19
+ n |= n >> 16;
20
+ return n + 1;
21
+}
22
+
23
+bool nipc_service_common_header_payload_len(size_t payload_len,
24
+ size_t *msg_len_out)
25
+{
26
+#if SIZE_MAX <= UINT32_MAX
27
+ if (payload_len > SIZE_MAX - NIPC_HEADER_LEN)
28
+ return false;
29
+#endif
30
+
31
+ *msg_len_out = NIPC_HEADER_LEN + payload_len;
32
+ return true;
33
+}
34
+
35
+bool nipc_service_common_header_payload_len_u32(uint32_t payload_len,
36
+ uint32_t *msg_len_out)
37
+{
38
+ if (payload_len > UINT32_MAX - NIPC_HEADER_LEN)
39
+ return false;
40
+
41
+ *msg_len_out = payload_len + NIPC_HEADER_LEN;
42
+ return true;
43
+}
44
+
45
+bool nipc_service_common_mul_would_overflow(size_t count, size_t size)
46
+{
47
+ return size != 0 && count > SIZE_MAX / size;
48
+}
49
+
50
+uint32_t nipc_service_common_request_payload_default(void)
51
+{
52
+ return NIPC_MAX_PAYLOAD_DEFAULT;
53
+}
54
+
55
+uint32_t nipc_service_common_response_payload_default(void)
56
+{
57
+ return NIPC_SERVICE_RESPONSE_BUF_DEFAULT;
58
+}
59
+
60
+/* Typed services expose one batch-count knob; level 1 keeps request/response counts symmetric. */
61
+uint32_t nipc_service_common_typed_response_batch_items(uint32_t max_request_batch_items)
62
+{
63
+ return max_request_batch_items;
64
+}
65
+
66
+void nipc_service_common_copy_cstr_field(char *dst, size_t dst_size, const char *src)
67
+{
68
+ if (!dst || dst_size == 0)
69
+ return;
70
+
71
+ dst[0] = '\0';
72
+ if (!src)
73
+ return;
74
+
75
+ size_t len = 0;
76
+ size_t max_copy = dst_size - 1;
77
+ while (len < max_copy && src[len] != '\0')
78
+ len++;
79
+
80
+ memcpy(dst, src, len);
81
+ dst[len] = '\0';
82
+}
83
+
84
+bool nipc_service_common_client_transport_fields(
85
+ nipc_service_common_transport_fields_t *fields,
86
+ const nipc_client_config_t *config)
87
+{
88
+ memset(fields, 0, sizeof(*fields));
89
+ if (!config)
90
+ return false;
91
+
92
+ fields->supported_profiles = config->supported_profiles;
93
+ fields->preferred_profiles = config->preferred_profiles;
94
+ fields->max_request_batch_items = config->max_request_batch_items;
95
+ fields->max_response_payload_bytes = config->max_response_payload_bytes;
96
+ fields->max_response_batch_items =
97
+ nipc_service_common_typed_response_batch_items(config->max_request_batch_items);
98
+ fields->auth_token = config->auth_token;
99
+ return true;
100
+}
101
+
102
+bool nipc_service_common_server_transport_fields(
103
+ nipc_service_common_transport_fields_t *fields,
104
+ const nipc_server_config_t *config)
105
+{
106
+ memset(fields, 0, sizeof(*fields));
107
+ if (!config)
108
+ return false;
109
+
110
+ fields->supported_profiles = config->supported_profiles;
111
+ fields->preferred_profiles = config->preferred_profiles;
112
+ fields->max_request_batch_items = config->max_request_batch_items;
113
+ fields->max_response_payload_bytes = config->max_response_payload_bytes;
114
+ fields->max_response_batch_items =
115
+ nipc_service_common_typed_response_batch_items(config->max_request_batch_items);
116
+ fields->auth_token = config->auth_token;
117
+ return true;
118
+}
119
+
120
+void nipc_service_common_client_init(nipc_client_ctx_t *ctx,
121
+ const char *run_dir,
122
+ const char *service_name)
123
+{
124
+ memset(ctx, 0, sizeof(*ctx));
125
+ ctx->state = NIPC_CLIENT_DISCONNECTED;
126
+ ctx->session_valid = false;
127
+ ctx->shm = NULL;
128
+ nipc_service_common_copy_cstr_field(ctx->run_dir, sizeof(ctx->run_dir), run_dir);
129
+ nipc_service_common_copy_cstr_field(ctx->service_name, sizeof(ctx->service_name), service_name);
130
+}
131
+
132
+void nipc_service_common_client_status(const nipc_client_ctx_t *ctx,
133
+ nipc_client_status_t *out)
134
+{
135
+ out->state = ctx->state;
136
+ out->connect_count = ctx->connect_count;
137
+ out->reconnect_count = ctx->reconnect_count;
138
+ out->call_count = ctx->call_count;
139
+ out->error_count = ctx->error_count;
140
+}
141
+
142
+void nipc_service_common_client_close_buffers(nipc_client_ctx_t *ctx)
143
+{
144
+ free(ctx->response_buf);
145
+ free(ctx->send_buf);
146
+ ctx->response_buf = NULL;
147
+ ctx->send_buf = NULL;
148
+ ctx->response_buf_size = 0;
149
+ ctx->send_buf_size = 0;
150
+ ctx->state = NIPC_CLIENT_DISCONNECTED;
151
+}
152
+
153
+bool nipc_service_common_client_refresh(nipc_client_ctx_t *ctx,
154
+ const nipc_service_common_client_ops_t *ops)
155
+{
156
+ nipc_client_state_t old_state = ctx->state;
157
+
158
+ switch (ctx->state) {
159
+ case NIPC_CLIENT_DISCONNECTED:
160
+ case NIPC_CLIENT_NOT_FOUND:
161
+ ctx->state = NIPC_CLIENT_CONNECTING;
162
+ ctx->state = ops->try_connect(ctx);
163
+ if (ctx->state == NIPC_CLIENT_READY)
164
+ ctx->connect_count++;
165
+ break;
166
+
167
+ case NIPC_CLIENT_BROKEN:
168
+ ops->disconnect(ctx);
169
+ ctx->state = NIPC_CLIENT_CONNECTING;
170
+ ctx->state = ops->try_connect(ctx);
171
+ if (ctx->state == NIPC_CLIENT_READY)
172
+ ctx->reconnect_count++;
173
+ break;
174
+
175
+ case NIPC_CLIENT_READY:
176
+ case NIPC_CLIENT_CONNECTING:
177
+ case NIPC_CLIENT_AUTH_FAILED:
178
+ case NIPC_CLIENT_INCOMPATIBLE:
179
+ break;
180
+ }
181
+
182
+ return ctx->state != old_state;
183
+}
184
+
185
+void nipc_service_common_client_note_request_capacity(nipc_client_ctx_t *ctx,
186
+ uint32_t payload_len)
187
+{
188
+ uint32_t grown = nipc_service_common_next_power_of_2_u32(payload_len);
189
+ if (grown > NIPC_MAX_PAYLOAD_CAP)
190
+ grown = NIPC_MAX_PAYLOAD_CAP;
191
+ if (grown > ctx->transport_config.max_request_payload_bytes)
192
+ ctx->transport_config.max_request_payload_bytes = grown;
193
+}
194
+
195
+void nipc_service_common_client_note_response_capacity(nipc_client_ctx_t *ctx,
196
+ uint32_t payload_len)
197
+{
198
+ uint32_t grown = nipc_service_common_next_power_of_2_u32(payload_len);
199
+ if (grown > NIPC_MAX_PAYLOAD_CAP)
200
+ grown = NIPC_MAX_PAYLOAD_CAP;
201
+ if (grown > ctx->transport_config.max_response_payload_bytes)
202
+ ctx->transport_config.max_response_payload_bytes = grown;
203
+}
204
+
205
+nipc_error_t nipc_service_common_client_prepare_shm_request(
206
+ nipc_client_ctx_t *ctx,
207
+ nipc_header_t *hdr,
208
+ const void *payload,
209
+ size_t payload_len,
210
+ uint8_t **msg_out,
211
+ size_t *msg_len_out)
212
+{
213
+ if (payload_len > UINT32_MAX)
214
+ return NIPC_ERR_OVERFLOW;
215
+
216
+ if (payload_len > ctx->session.max_request_payload_bytes) {
217
+ nipc_service_common_client_note_request_capacity(
218
+ ctx, (uint32_t)payload_len);
219
+ return NIPC_ERR_OVERFLOW;
220
+ }
221
+
222
+ size_t msg_len;
223
+ if (!nipc_service_common_header_payload_len(payload_len, &msg_len))
224
+ return NIPC_ERR_OVERFLOW;
225
+
226
+ uint8_t *msg = ctx->send_buf;
227
+ if (!msg || msg_len > ctx->send_buf_size)
228
+ return NIPC_ERR_OVERFLOW;
229
+
230
+ if (payload_len > 0)
231
+ memmove(msg + NIPC_HEADER_LEN, payload, payload_len);
232
+
233
+ hdr->magic = NIPC_MAGIC_MSG;
234
+ hdr->version = NIPC_VERSION;
235
+ hdr->header_len = NIPC_HEADER_LEN;
236
+ hdr->payload_len = (uint32_t)payload_len;
237
+
238
+ nipc_header_encode(hdr, msg, NIPC_HEADER_LEN);
239
+
240
+ *msg_out = msg;
241
+ *msg_len_out = msg_len;
242
+ return NIPC_OK;
243
+}
244
+
245
+nipc_error_t nipc_service_common_client_parse_shm_response(
246
+ void *buf,
247
+ size_t msg_len,
248
+ nipc_header_t *hdr_out,
249
+ const void **payload_out,
250
+ size_t *payload_len_out)
251
+{
252
+ if (msg_len < NIPC_HEADER_LEN)
253
+ return NIPC_ERR_TRUNCATED;
254
+
255
+ nipc_error_t perr = nipc_header_decode(buf, msg_len, hdr_out);
256
+ if (perr != NIPC_OK)
257
+ return perr;
258
+
259
+ *payload_out = (const uint8_t *)buf + NIPC_HEADER_LEN;
260
+ *payload_len_out = msg_len - NIPC_HEADER_LEN;
261
+ return NIPC_OK;
262
+}
263
+
264
+nipc_error_t nipc_service_common_response_status_to_error(nipc_client_ctx_t *ctx,
265
+ const nipc_header_t *resp_hdr)
266
+{
267
+ switch (resp_hdr->transport_status) {
268
+ case NIPC_STATUS_OK:
269
+ return NIPC_OK;
270
+ case NIPC_STATUS_LIMIT_EXCEEDED:
271
+ if (ctx->session.max_response_payload_bytes > 0) {
272
+ uint32_t current = ctx->session.max_response_payload_bytes;
273
+ nipc_service_common_client_note_response_capacity(
274
+ ctx, current >= UINT32_MAX / 2u ? UINT32_MAX : current * 2u);
275
+ }
276
+ return NIPC_ERR_OVERFLOW;
277
+ case NIPC_STATUS_UNSUPPORTED:
278
+ return NIPC_ERR_BAD_LAYOUT;
279
+ case NIPC_STATUS_BAD_ENVELOPE:
280
+ case NIPC_STATUS_INTERNAL_ERROR:
281
+ default:
282
+ return NIPC_ERR_BAD_LAYOUT;
283
+ }
284
+}
285
+
286
+nipc_error_t nipc_service_common_do_raw_call(
287
+ nipc_client_ctx_t *ctx,
288
+ uint16_t method_code,
289
+ const void *request_payload,
290
+ size_t request_len,
291
+ const void **response_payload_out,
292
+ size_t *response_len_out,
293
+ nipc_service_common_transport_send_fn send_fn,
294
+ nipc_service_common_transport_receive_fn receive_fn)
295
+{
296
+ nipc_header_t hdr = {0};
297
+ hdr.kind = NIPC_KIND_REQUEST;
298
+ hdr.code = method_code;
299
+ hdr.flags = 0;
300
+ hdr.item_count = 1;
301
+ hdr.message_id = (uint64_t)(ctx->call_count + 1);
302
+ hdr.transport_status = NIPC_STATUS_OK;
303
+
304
+ nipc_error_t err = send_fn(ctx, &hdr, request_payload, request_len);
305
+ if (err != NIPC_OK)
306
+ return err;
307
+
308
+ nipc_header_t resp_hdr;
309
+ err = receive_fn(ctx, ctx->response_buf, ctx->response_buf_size,
310
+ &resp_hdr, response_payload_out, response_len_out);
311
+ if (err != NIPC_OK)
312
+ return err;
313
+
314
+ if (resp_hdr.kind != NIPC_KIND_RESPONSE)
315
+ return NIPC_ERR_BAD_KIND;
316
+ if (resp_hdr.code != method_code)
317
+ return NIPC_ERR_BAD_LAYOUT;
318
+ if (resp_hdr.message_id != hdr.message_id)
319
+ return NIPC_ERR_BAD_LAYOUT;
320
+ return nipc_service_common_response_status_to_error(ctx, &resp_hdr);
321
+}
322
+
323
+nipc_error_t nipc_service_common_call_with_retry(
324
+ nipc_client_ctx_t *ctx,
325
+ nipc_service_common_attempt_fn attempt,
326
+ void *state,
327
+ const nipc_service_common_client_ops_t *ops)
328
+{
329
+ if (ctx->state != NIPC_CLIENT_READY) {
330
+ ctx->error_count++;
331
+ return NIPC_ERR_NOT_READY;
332
+ }
333
+
334
+ int overflow_retries = 0;
335
+ for (;;) {
336
+ uint32_t prev_req = ctx->session.max_request_payload_bytes;
337
+ uint32_t prev_resp = ctx->session.max_response_payload_bytes;
338
+ uint32_t prev_cfg_req = ctx->transport_config.max_request_payload_bytes;
339
+ uint32_t prev_cfg_resp = ctx->transport_config.max_response_payload_bytes;
340
+
341
+ nipc_error_t err = attempt(ctx, state);
342
+ if (err == NIPC_OK) {
343
+ ctx->call_count++;
344
+ return NIPC_OK;
345
+ }
346
+
347
+ if (err != NIPC_ERR_OVERFLOW) {
348
+ ops->disconnect(ctx);
349
+ ctx->state = NIPC_CLIENT_BROKEN;
350
+ if (ops->reconnect_drain_ms > 0 && ops->sleep_ms)
351
+ ops->sleep_ms(ops->reconnect_drain_ms);
352
+ if (!ops->reconnect_for_call(ctx)) {
353
+ ctx->error_count++;
354
+ return err;
355
+ }
356
+
357
+ ctx->reconnect_count++;
358
+ if (ops->sleep_ms && ops->reconnect_retry_interval_ms > 0)
359
+ ops->sleep_ms(ops->reconnect_retry_interval_ms);
360
+ err = attempt(ctx, state);
361
+ if (err == NIPC_OK) {
362
+ ctx->call_count++;
363
+ return NIPC_OK;
364
+ }
365
+
366
+ ops->disconnect(ctx);
367
+ ctx->state = NIPC_CLIENT_BROKEN;
368
+ ctx->error_count++;
369
+ return err;
370
+ }
371
+
372
+ ops->disconnect(ctx);
373
+ ctx->state = NIPC_CLIENT_BROKEN;
374
+ if (!ops->reconnect_for_call(ctx)) {
375
+ ctx->error_count++;
376
+ return err;
377
+ }
378
+ ctx->reconnect_count++;
379
+
380
+ if (ctx->session.max_request_payload_bytes <= prev_req &&
381
+ ctx->session.max_response_payload_bytes <= prev_resp &&
382
+ ctx->transport_config.max_request_payload_bytes <= prev_cfg_req &&
383
+ ctx->transport_config.max_response_payload_bytes <= prev_cfg_resp) {
384
+ ops->disconnect(ctx);
385
+ ctx->state = NIPC_CLIENT_BROKEN;
386
+ ctx->error_count++;
387
+ return err;
388
+ }
389
+
390
+ if (++overflow_retries >= 8) {
391
+ ops->disconnect(ctx);
392
+ ctx->state = NIPC_CLIENT_BROKEN;
393
+ ctx->error_count++;
394
+ return err;
395
+ }
396
+ if (ops->sleep_ms && ops->reconnect_retry_interval_ms > 0)
397
+ ops->sleep_ms(ops->reconnect_retry_interval_ms);
398
+ }
399
+}
400
+
401
+nipc_error_t nipc_service_common_server_init_base(
402
+ nipc_managed_server_t *server,
403
+ const char *run_dir,
404
+ const char *service_name,
405
+ int worker_count,
406
+ uint16_t expected_method_code,
407
+ nipc_server_handler_fn handler,
408
+ void *user,
409
+ uint32_t max_request_payload_bytes,
410
+ uint32_t max_response_payload_bytes)
411
+{
412
+ if (!run_dir || !service_name || !handler)
413
+ return NIPC_ERR_BAD_LAYOUT;
414
+
415
+ if (worker_count < 1)
416
+ worker_count = 1;
417
+
418
+ nipc_service_common_copy_cstr_field(server->run_dir, sizeof(server->run_dir),
419
+ run_dir);
420
+ nipc_service_common_copy_cstr_field(server->service_name,
421
+ sizeof(server->service_name),
422
+ service_name);
423
+
424
+ server->handler = handler;
425
+ server->handler_user = user;
426
+ server->worker_count = worker_count;
427
+ server->expected_method_code = expected_method_code;
428
+ server->learned_request_payload_bytes =
429
+ max_request_payload_bytes > 0
430
+ ? max_request_payload_bytes
431
+ : NIPC_MAX_PAYLOAD_DEFAULT;
432
+ server->learned_response_payload_bytes =
433
+ max_response_payload_bytes > 0
434
+ ? max_response_payload_bytes
435
+ : NIPC_MAX_PAYLOAD_DEFAULT;
436
+ server->session_capacity = worker_count * 2;
437
+ if (server->session_capacity < 16)
438
+ server->session_capacity = 16;
439
+ server->session_count = 0;
440
+ server->next_session_id = 1;
441
+ return NIPC_OK;
442
+}
443
+
444
+nipc_error_t nipc_service_common_server_alloc_sessions(
445
+ nipc_managed_server_t *server,
446
+ nipc_service_common_calloc_fn calloc_fn,
447
+ int fault_site)
448
+{
449
+ server->sessions = calloc_fn((size_t)server->session_capacity,
450
+ sizeof(nipc_session_ctx_t *),
451
+ fault_site);
452
+ return server->sessions ? NIPC_OK : NIPC_ERR_OVERFLOW;
453
+}
454
+
455
+void nipc_service_common_server_note_request_capacity(nipc_managed_server_t *server,
456
+ uint32_t payload_len)
457
+{
458
+ uint32_t grown = nipc_service_common_next_power_of_2_u32(payload_len);
459
+#if defined(_WIN32) || defined(__MSYS__)
460
+ uint32_t current = server->learned_request_payload_bytes;
461
+ while (grown > current) {
462
+ uint32_t previous = (uint32_t)InterlockedCompareExchange(
463
+ (volatile LONG *)&server->learned_request_payload_bytes,
464
+ (LONG)grown, (LONG)current);
465
+ if (previous == current)
466
+ break;
467
+ current = previous;
468
+ }
469
+#else
470
+ uint32_t current = __atomic_load_n(&server->learned_request_payload_bytes,
471
+ __ATOMIC_RELAXED);
472
+ while (grown > current &&
473
+ !__atomic_compare_exchange_n(&server->learned_request_payload_bytes,
474
+ ¤t, grown, false,
475
+ __ATOMIC_RELEASE, __ATOMIC_RELAXED)) {
476
+ }
477
+#endif
478
+}
479
+
480
+void nipc_service_common_server_note_response_capacity(nipc_managed_server_t *server,
481
+ uint32_t payload_len)
482
+{
483
+ uint32_t grown = nipc_service_common_next_power_of_2_u32(payload_len);
484
+#if defined(_WIN32) || defined(__MSYS__)
485
+ uint32_t current = server->learned_response_payload_bytes;
486
+ while (grown > current) {
487
+ uint32_t previous = (uint32_t)InterlockedCompareExchange(
488
+ (volatile LONG *)&server->learned_response_payload_bytes,
489
+ (LONG)grown, (LONG)current);
490
+ if (previous == current)
491
+ break;
492
+ current = previous;
493
+ }
494
+#else
495
+ uint32_t current = __atomic_load_n(&server->learned_response_payload_bytes,
496
+ __ATOMIC_RELAXED);
497
+ while (grown > current &&
498
+ !__atomic_compare_exchange_n(&server->learned_response_payload_bytes,
499
+ ¤t, grown, false,
500
+ __ATOMIC_RELEASE, __ATOMIC_RELAXED)) {
501
+ }
502
+#endif
503
+}
504
+
505
+void nipc_service_common_prepare_response_header(const nipc_header_t *request_hdr,
506
+ nipc_header_t *resp_hdr)
507
+{
508
+ memset(resp_hdr, 0, sizeof(*resp_hdr));
509
+ resp_hdr->kind = NIPC_KIND_RESPONSE;
510
+ resp_hdr->code = request_hdr->code;
511
+ resp_hdr->message_id = request_hdr->message_id;
512
+ if ((request_hdr->flags & NIPC_FLAG_BATCH) && request_hdr->item_count >= 1) {
513
+ resp_hdr->item_count = request_hdr->item_count;
514
+ resp_hdr->flags = NIPC_FLAG_BATCH;
515
+ } else {
516
+ resp_hdr->item_count = 1;
517
+ resp_hdr->flags = 0;
518
+ }
519
+}
520
+
521
+void nipc_service_common_apply_dispatch_result(nipc_managed_server_t *server,
522
+ nipc_error_t dispatch_err,
523
+ size_t resp_buf_size,
524
+ uint32_t max_response_payload_bytes,
525
+ bool check_header_room,
526
+ nipc_header_t *resp_hdr,
527
+ size_t *response_len,
528
+ bool *close_after_response)
529
+{
530
+ *close_after_response = false;
531
+
532
+ switch (dispatch_err) {
533
+ case NIPC_OK:
534
+ if (*response_len > resp_buf_size ||
535
+ *response_len > max_response_payload_bytes ||
536
+ (check_header_room && *response_len > SIZE_MAX - NIPC_HEADER_LEN)) {
537
+ nipc_service_common_server_note_response_capacity(
538
+ server, *response_len >= UINT32_MAX ? UINT32_MAX : (uint32_t)*response_len);
539
+ resp_hdr->transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
540
+ *close_after_response = true;
541
+ *response_len = 0;
542
+ } else {
543
+ if (*response_len <= UINT32_MAX)
544
+ nipc_service_common_server_note_response_capacity(
545
+ server, (uint32_t)*response_len);
546
+ resp_hdr->transport_status = NIPC_STATUS_OK;
547
+ }
548
+ break;
549
+ case NIPC_ERR_OVERFLOW:
550
+ if (max_response_payload_bytes >= UINT32_MAX / 2u)
551
+ nipc_service_common_server_note_response_capacity(server, UINT32_MAX);
552
+ else
553
+ nipc_service_common_server_note_response_capacity(
554
+ server, max_response_payload_bytes * 2u);
555
+ resp_hdr->transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
556
+ *close_after_response = true;
557
+ *response_len = 0;
558
+ break;
559
+ case NIPC_ERR_TRUNCATED:
560
+ case NIPC_ERR_BAD_LAYOUT:
561
+ case NIPC_ERR_OUT_OF_BOUNDS:
562
+ case NIPC_ERR_MISSING_NUL:
563
+ case NIPC_ERR_BAD_ALIGNMENT:
564
+ case NIPC_ERR_BAD_ITEM_COUNT:
565
+ resp_hdr->transport_status = NIPC_STATUS_BAD_ENVELOPE;
566
+ *close_after_response = true;
567
+ *response_len = 0;
568
+ break;
569
+ case NIPC_ERR_HANDLER_FAILED:
570
+ default:
571
+ resp_hdr->transport_status = NIPC_STATUS_INTERNAL_ERROR;
572
+ *close_after_response = true;
573
+ *response_len = 0;
574
+ break;
575
+ }
576
+}
src/libnetdata/netipc/src/service/netipc_service_common.h
new
+162
@@ -0,0 +1,162 @@
1
+#ifndef NETIPC_SERVICE_COMMON_H
2
+#define NETIPC_SERVICE_COMMON_H
3
+
4
+#include "netipc/netipc_service.h"
5
+
6
+#include <stdbool.h>
7
+#include <stddef.h>
8
+#include <stdint.h>
9
+
10
+#ifdef __cplusplus
11
+extern "C" {
12
+#endif
13
+
14
+typedef struct {
15
+ void *(*malloc_fn)(size_t size, int fault_site);
16
+ void *(*calloc_fn)(size_t count, size_t size, int fault_site);
17
+ uint64_t (*monotonic_ms_fn)(void);
18
+ int cache_buckets_fault_site;
19
+ int cache_items_fault_site;
20
+ int cache_item_name_fault_site;
21
+ int cache_item_path_fault_site;
22
+} nipc_service_common_cache_ops_t;
23
+
24
+typedef nipc_error_t (*nipc_service_common_attempt_fn)(nipc_client_ctx_t *ctx,
25
+ void *state);
26
+typedef nipc_error_t (*nipc_service_common_transport_send_fn)(
27
+ nipc_client_ctx_t *ctx,
28
+ nipc_header_t *hdr,
29
+ const void *payload,
30
+ size_t payload_len);
31
+typedef nipc_error_t (*nipc_service_common_transport_receive_fn)(
32
+ nipc_client_ctx_t *ctx,
33
+ void *buf,
34
+ size_t buf_size,
35
+ nipc_header_t *hdr_out,
36
+ const void **payload_out,
37
+ size_t *payload_len_out);
38
+
39
+typedef struct {
40
+ void (*disconnect)(nipc_client_ctx_t *ctx);
41
+ nipc_client_state_t (*try_connect)(nipc_client_ctx_t *ctx);
42
+ bool (*reconnect_for_call)(nipc_client_ctx_t *ctx);
43
+ void (*sleep_ms)(uint32_t ms);
44
+ uint32_t reconnect_drain_ms;
45
+ uint32_t reconnect_retry_interval_ms;
46
+} nipc_service_common_client_ops_t;
47
+
48
+typedef void *(*nipc_service_common_calloc_fn)(size_t count,
49
+ size_t size,
50
+ int fault_site);
51
+
52
+typedef struct {
53
+ uint32_t supported_profiles;
54
+ uint32_t preferred_profiles;
55
+ uint32_t max_request_batch_items;
56
+ uint32_t max_response_payload_bytes;
57
+ uint32_t max_response_batch_items;
58
+ uint64_t auth_token;
59
+} nipc_service_common_transport_fields_t;
60
+
61
+#define NIPC_SERVICE_COMMON_APPLY_TRANSPORT_FIELDS(dst, fields) do { \
62
+ (dst)->supported_profiles = (fields)->supported_profiles; \
63
+ (dst)->preferred_profiles = (fields)->preferred_profiles; \
64
+ (dst)->max_request_batch_items = (fields)->max_request_batch_items; \
65
+ (dst)->max_response_payload_bytes = (fields)->max_response_payload_bytes; \
66
+ (dst)->max_response_batch_items = (fields)->max_response_batch_items; \
67
+ (dst)->auth_token = (fields)->auth_token; \
68
+} while (0)
69
+
70
+uint32_t nipc_service_common_next_power_of_2_u32(uint32_t n);
71
+bool nipc_service_common_header_payload_len(size_t payload_len,
72
+ size_t *msg_len_out);
73
+bool nipc_service_common_header_payload_len_u32(uint32_t payload_len,
74
+ uint32_t *msg_len_out);
75
+bool nipc_service_common_mul_would_overflow(size_t count, size_t size);
76
+uint32_t nipc_service_common_request_payload_default(void);
77
+uint32_t nipc_service_common_response_payload_default(void);
78
+uint32_t nipc_service_common_typed_response_batch_items(uint32_t max_request_batch_items);
79
+void nipc_service_common_copy_cstr_field(char *dst, size_t dst_size, const char *src);
80
+bool nipc_service_common_client_transport_fields(
81
+ nipc_service_common_transport_fields_t *fields,
82
+ const nipc_client_config_t *config);
83
+bool nipc_service_common_server_transport_fields(
84
+ nipc_service_common_transport_fields_t *fields,
85
+ const nipc_server_config_t *config);
86
+
87
+void nipc_service_common_client_init(nipc_client_ctx_t *ctx,
88
+ const char *run_dir,
89
+ const char *service_name);
90
+void nipc_service_common_client_status(const nipc_client_ctx_t *ctx,
91
+ nipc_client_status_t *out);
92
+void nipc_service_common_client_close_buffers(nipc_client_ctx_t *ctx);
93
+bool nipc_service_common_client_refresh(nipc_client_ctx_t *ctx,
94
+ const nipc_service_common_client_ops_t *ops);
95
+void nipc_service_common_client_note_request_capacity(nipc_client_ctx_t *ctx,
96
+ uint32_t payload_len);
97
+void nipc_service_common_client_note_response_capacity(nipc_client_ctx_t *ctx,
98
+ uint32_t payload_len);
99
+nipc_error_t nipc_service_common_client_prepare_shm_request(
100
+ nipc_client_ctx_t *ctx,
101
+ nipc_header_t *hdr,
102
+ const void *payload,
103
+ size_t payload_len,
104
+ uint8_t **msg_out,
105
+ size_t *msg_len_out);
106
+nipc_error_t nipc_service_common_client_parse_shm_response(
107
+ void *buf,
108
+ size_t msg_len,
109
+ nipc_header_t *hdr_out,
110
+ const void **payload_out,
111
+ size_t *payload_len_out);
112
+nipc_error_t nipc_service_common_response_status_to_error(nipc_client_ctx_t *ctx,
113
+ const nipc_header_t *resp_hdr);
114
+nipc_error_t nipc_service_common_do_raw_call(
115
+ nipc_client_ctx_t *ctx,
116
+ uint16_t method_code,
117
+ const void *request_payload,
118
+ size_t request_len,
119
+ const void **response_payload_out,
120
+ size_t *response_len_out,
121
+ nipc_service_common_transport_send_fn send_fn,
122
+ nipc_service_common_transport_receive_fn receive_fn);
123
+nipc_error_t nipc_service_common_call_with_retry(
124
+ nipc_client_ctx_t *ctx,
125
+ nipc_service_common_attempt_fn attempt,
126
+ void *state,
127
+ const nipc_service_common_client_ops_t *ops);
128
+
129
+void nipc_service_common_server_note_request_capacity(nipc_managed_server_t *server,
130
+ uint32_t payload_len);
131
+void nipc_service_common_server_note_response_capacity(nipc_managed_server_t *server,
132
+ uint32_t payload_len);
133
+nipc_error_t nipc_service_common_server_init_base(
134
+ nipc_managed_server_t *server,
135
+ const char *run_dir,
136
+ const char *service_name,
137
+ int worker_count,
138
+ uint16_t expected_method_code,
139
+ nipc_server_handler_fn handler,
140
+ void *user,
141
+ uint32_t max_request_payload_bytes,
142
+ uint32_t max_response_payload_bytes);
143
+nipc_error_t nipc_service_common_server_alloc_sessions(
144
+ nipc_managed_server_t *server,
145
+ nipc_service_common_calloc_fn calloc_fn,
146
+ int fault_site);
147
+void nipc_service_common_prepare_response_header(const nipc_header_t *request_hdr,
148
+ nipc_header_t *resp_hdr);
149
+void nipc_service_common_apply_dispatch_result(nipc_managed_server_t *server,
150
+ nipc_error_t dispatch_err,
151
+ size_t resp_buf_size,
152
+ uint32_t max_response_payload_bytes,
153
+ bool check_header_room,
154
+ nipc_header_t *resp_hdr,
155
+ size_t *response_len,
156
+ bool *close_after_response);
157
+
158
+#ifdef __cplusplus
159
+}
160
+#endif
161
+
162
+#endif /* NETIPC_SERVICE_COMMON_H */
src/libnetdata/netipc/src/service/netipc_service_platform.h
new
+63
@@ -0,0 +1,63 @@
1
+#ifndef NETIPC_SERVICE_PLATFORM_H
2
+#define NETIPC_SERVICE_PLATFORM_H
3
+
4
+#include "netipc_service_common.h"
5
+
6
+#ifdef __cplusplus
7
+extern "C" {
8
+#endif
9
+
10
+#if defined(_WIN32) || defined(__MSYS__)
11
+typedef nipc_np_server_config_t nipc_service_platform_server_config_t;
12
+#else
13
+typedef nipc_uds_server_config_t nipc_service_platform_server_config_t;
14
+#endif
15
+
16
+typedef nipc_service_common_attempt_fn nipc_service_platform_attempt_fn;
17
+
18
+enum {
19
+ NIPC_SERVICE_PLATFORM_TEST_FAULT_CACHE_BUCKETS_CALLOC_INTERNAL = 10,
20
+ NIPC_SERVICE_PLATFORM_TEST_FAULT_CACHE_ITEMS_CALLOC_INTERNAL,
21
+ NIPC_SERVICE_PLATFORM_TEST_FAULT_CACHE_ITEM_NAME_MALLOC_INTERNAL,
22
+ NIPC_SERVICE_PLATFORM_TEST_FAULT_CACHE_ITEM_PATH_MALLOC_INTERNAL,
23
+};
24
+
25
+void *nipc_service_platform_malloc(size_t size, int fault_site);
26
+void *nipc_service_platform_calloc(size_t count, size_t size, int fault_site);
27
+uint64_t nipc_service_platform_monotonic_ms(void);
28
+
29
+bool nipc_service_platform_ensure_client_send_buffer(nipc_client_ctx_t *ctx,
30
+ size_t need);
31
+
32
+nipc_error_t nipc_service_platform_do_raw_call(
33
+ nipc_client_ctx_t *ctx,
34
+ uint16_t method_code,
35
+ const void *request_payload,
36
+ size_t request_len,
37
+ const void **response_payload_out,
38
+ size_t *response_len_out);
39
+
40
+nipc_error_t nipc_service_platform_call_with_retry(
41
+ nipc_client_ctx_t *ctx,
42
+ nipc_service_platform_attempt_fn attempt,
43
+ void *state);
44
+
45
+void nipc_service_platform_server_config_from_service(
46
+ nipc_service_platform_server_config_t *transport,
47
+ const nipc_server_config_t *config);
48
+
49
+nipc_error_t nipc_service_platform_server_init_raw(
50
+ nipc_managed_server_t *server,
51
+ const char *run_dir,
52
+ const char *service_name,
53
+ const nipc_service_platform_server_config_t *config,
54
+ int worker_count,
55
+ uint16_t expected_method_code,
56
+ nipc_server_handler_fn handler,
57
+ void *user);
58
+
59
+#ifdef __cplusplus
60
+}
61
+#endif
62
+
63
+#endif /* NETIPC_SERVICE_PLATFORM_H */
src/libnetdata/netipc/src/service/netipc_service_posix_client.c
new
+97
@@ -0,0 +1,97 @@
1
+/*
2
+ * netipc_service_posix_client.c - POSIX public service client API.
3
+ */
4
+
5
+#include "netipc/netipc_service.h"
6
+#include "netipc/netipc_protocol.h"
7
+#include "netipc/netipc_uds.h"
8
+#include "netipc/netipc_shm.h"
9
+#include "netipc_service_common.h"
10
+#include "netipc_service_platform.h"
11
+#include "netipc_service_posix_internal.h"
12
+
13
+#include <stdint.h>
14
+#include <stdlib.h>
15
+#include <string.h>
16
+
17
+static nipc_uds_client_config_t service_client_config_to_transport(
18
+ const nipc_client_config_t *config)
19
+{
20
+ nipc_uds_client_config_t transport = {0};
21
+ nipc_service_common_transport_fields_t fields;
22
+
23
+ if (!nipc_service_common_client_transport_fields(&fields, config))
24
+ return transport;
25
+
26
+ NIPC_SERVICE_COMMON_APPLY_TRANSPORT_FIELDS(&transport, &fields);
27
+ return transport;
28
+}
29
+
30
+static void client_sleep_ms(uint32_t ms)
31
+{
32
+ nipc_service_posix_sleep_us(ms * 1000u);
33
+}
34
+
35
+const nipc_service_common_client_ops_t *nipc_service_posix_client_ops(void)
36
+{
37
+ static const nipc_service_common_client_ops_t ops = {
38
+ .disconnect = nipc_service_posix_client_disconnect,
39
+ .try_connect = nipc_service_posix_client_try_connect,
40
+ .reconnect_for_call = nipc_service_posix_client_reconnect_for_call,
41
+ .sleep_ms = client_sleep_ms,
42
+ .reconnect_drain_ms = CLIENT_CALL_RECONNECT_DRAIN_MS,
43
+ .reconnect_retry_interval_ms = CLIENT_CALL_RECONNECT_RETRY_INTERVAL_MS,
44
+ };
45
+ return &ops;
46
+}
47
+
48
+void nipc_service_platform_server_config_from_service(
49
+ nipc_service_platform_server_config_t *transport,
50
+ const nipc_server_config_t *config)
51
+{
52
+ memset(transport, 0, sizeof(*transport));
53
+ nipc_service_common_transport_fields_t fields;
54
+
55
+ if (!nipc_service_common_server_transport_fields(&fields, config))
56
+ return;
57
+
58
+ NIPC_SERVICE_COMMON_APPLY_TRANSPORT_FIELDS(transport, &fields);
59
+}
60
+
61
+/* ------------------------------------------------------------------ */
62
+/* Public API: client lifecycle */
63
+/* ------------------------------------------------------------------ */
64
+
65
+void nipc_client_init(nipc_client_ctx_t *ctx,
66
+ const char *run_dir,
67
+ const char *service_name,
68
+ const nipc_client_config_t *config)
69
+{
70
+ nipc_service_common_client_init(ctx, run_dir, service_name);
71
+ ctx->session.fd = -1;
72
+
73
+ ctx->transport_config = service_client_config_to_transport(config);
74
+ if (ctx->transport_config.max_request_payload_bytes == 0)
75
+ ctx->transport_config.max_request_payload_bytes =
76
+ nipc_service_common_request_payload_default();
77
+ if (ctx->transport_config.max_response_payload_bytes == 0)
78
+ ctx->transport_config.max_response_payload_bytes =
79
+ nipc_service_common_response_payload_default();
80
+}
81
+
82
+bool nipc_client_refresh(nipc_client_ctx_t *ctx)
83
+{
84
+ return nipc_service_common_client_refresh(ctx, nipc_service_posix_client_ops());
85
+}
86
+
87
+void nipc_client_status(const nipc_client_ctx_t *ctx,
88
+ nipc_client_status_t *out)
89
+{
90
+ nipc_service_common_client_status(ctx, out);
91
+}
92
+
93
+void nipc_client_close(nipc_client_ctx_t *ctx)
94
+{
95
+ nipc_service_posix_client_disconnect(ctx);
96
+ nipc_service_common_client_close_buffers(ctx);
97
+}
src/libnetdata/netipc/src/service/netipc_service_posix_client_call.c
new
+130
@@ -0,0 +1,130 @@
1
+/*
2
+ * netipc_service_posix_client_call.c - POSIX raw client call flow.
3
+ */
4
+
5
+#include "netipc/netipc_service.h"
6
+#include "netipc/netipc_protocol.h"
7
+#include "netipc/netipc_uds.h"
8
+#include "netipc/netipc_shm.h"
9
+#include "netipc_service_common.h"
10
+#include "netipc_service_platform.h"
11
+#include "netipc_service_posix_internal.h"
12
+
13
+#include <stdint.h>
14
+#include <stdlib.h>
15
+#include <string.h>
16
+
17
+/* ------------------------------------------------------------------ */
18
+/* Internal: send/receive via the active transport */
19
+/* ------------------------------------------------------------------ */
20
+
21
+/*
22
+ * Send a complete message (header + payload) using whichever transport
23
+ * is active: SHM if negotiated, UDS otherwise.
24
+ */
25
+static nipc_error_t transport_send(nipc_client_ctx_t *ctx,
26
+ nipc_header_t *hdr,
27
+ const void *payload,
28
+ size_t payload_len)
29
+{
30
+ if (payload_len > UINT32_MAX)
31
+ return NIPC_ERR_OVERFLOW;
32
+
33
+ if (ctx->shm) {
34
+ uint8_t *msg;
35
+ size_t msg_len;
36
+ nipc_error_t perr = nipc_service_common_client_prepare_shm_request(
37
+ ctx, hdr, payload, payload_len, &msg, &msg_len);
38
+ if (perr != NIPC_OK)
39
+ return perr;
40
+
41
+ nipc_shm_error_t serr = nipc_shm_send(ctx->shm, msg, msg_len);
42
+ if (serr == NIPC_SHM_ERR_MSG_TOO_LARGE) {
43
+ nipc_service_common_client_note_request_capacity(
44
+ ctx, (uint32_t)payload_len);
45
+ return NIPC_ERR_OVERFLOW;
46
+ }
47
+ return (serr == NIPC_SHM_OK) ? NIPC_OK : NIPC_ERR_NOT_READY;
48
+ }
49
+
50
+ /* UDS path */
51
+ nipc_uds_error_t uerr = nipc_uds_send(&ctx->session, hdr,
52
+ payload, payload_len);
53
+ if (uerr == NIPC_UDS_ERR_LIMIT_EXCEEDED) {
54
+ nipc_service_common_client_note_request_capacity(
55
+ ctx, (uint32_t)payload_len);
56
+ return NIPC_ERR_OVERFLOW;
57
+ }
58
+ return (uerr == NIPC_UDS_OK) ? NIPC_OK : NIPC_ERR_NOT_READY;
59
+}
60
+
61
+/*
62
+ * Receive a complete message. For SHM, reads from the SHM region.
63
+ * For UDS, reads from the socket into the caller's buffer.
64
+ *
65
+ * On success, hdr_out is filled, and payload_out + payload_len_out
66
+ * point to the payload bytes (valid until next receive).
67
+ */
68
+static nipc_error_t transport_receive(nipc_client_ctx_t *ctx,
69
+ void *buf, size_t buf_size,
70
+ nipc_header_t *hdr_out,
71
+ const void **payload_out,
72
+ size_t *payload_len_out)
73
+{
74
+ if (ctx->shm) {
75
+ size_t msg_len;
76
+ nipc_shm_error_t serr = nipc_shm_receive(ctx->shm, buf, buf_size,
77
+ &msg_len, 30000);
78
+ if (serr != NIPC_SHM_OK)
79
+ return NIPC_ERR_TRUNCATED;
80
+
81
+ return nipc_service_common_client_parse_shm_response(
82
+ buf, msg_len, hdr_out, payload_out, payload_len_out);
83
+ }
84
+
85
+ /* UDS path */
86
+ nipc_uds_error_t uerr = nipc_uds_receive(&ctx->session, buf, buf_size,
87
+ hdr_out, payload_out,
88
+ payload_len_out);
89
+ return (uerr == NIPC_UDS_OK) ? NIPC_OK : NIPC_ERR_TRUNCATED;
90
+}
91
+
92
+/* ------------------------------------------------------------------ */
93
+/* Internal: generic raw call (send request, receive response) */
94
+/* ------------------------------------------------------------------ */
95
+
96
+/*
97
+ * Single-attempt raw call: build envelope, send, receive, validate
98
+ * envelope. The caller handles encode before and decode after.
99
+ *
100
+ * On success, response_payload_out and response_len_out point into the
101
+ * internal client response buffer (valid until next call on this context).
102
+ */
103
+nipc_error_t nipc_service_platform_do_raw_call(nipc_client_ctx_t *ctx,
104
+ uint16_t method_code,
105
+ const void *request_payload,
106
+ size_t request_len,
107
+ const void **response_payload_out,
108
+ size_t *response_len_out)
109
+{
110
+ return nipc_service_common_do_raw_call(
111
+ ctx, method_code, request_payload, request_len,
112
+ response_payload_out, response_len_out,
113
+ transport_send, transport_receive);
114
+}
115
+
116
+/*
117
+ * Generic call-with-retry:
118
+ * - ordinary failures reconnect and retry once
119
+ * - overflow-driven resize recovery may reconnect repeatedly until
120
+ * negotiated capacities grow or recovery fails
121
+ * The caller provides a function pointer for the single-attempt logic.
122
+ */
123
+nipc_error_t nipc_service_platform_call_with_retry(
124
+ nipc_client_ctx_t *ctx,
125
+ nipc_service_platform_attempt_fn attempt,
126
+ void *state)
127
+{
128
+ return nipc_service_common_call_with_retry(
129
+ ctx, attempt, state, nipc_service_posix_client_ops());
130
+}
src/libnetdata/netipc/src/service/netipc_service_posix_client_connect.c
new
+172
@@ -0,0 +1,172 @@
1
+/*
2
+ * netipc_service_posix_client_connect.c - POSIX client connection management.
3
+ */
4
+
5
+#include "netipc/netipc_service.h"
6
+#include "netipc/netipc_protocol.h"
7
+#include "netipc/netipc_uds.h"
8
+#include "netipc/netipc_shm.h"
9
+#include "netipc_service_common.h"
10
+#include "netipc_service_platform.h"
11
+#include "netipc_service_posix_internal.h"
12
+
13
+#include <stdint.h>
14
+#include <stdlib.h>
15
+#include <string.h>
16
+
17
+static bool client_prepare_session_buffers(nipc_client_ctx_t *ctx)
18
+{
19
+ size_t response_need;
20
+ if (!nipc_service_common_header_payload_len(
21
+ ctx->session.max_response_payload_bytes, &response_need))
22
+ return false;
23
+ if (response_need < NIPC_HEADER_LEN + 1024u)
24
+ response_need = NIPC_HEADER_LEN + 1024u;
25
+
26
+ if (!nipc_service_posix_ensure_buffer(&ctx->response_buf, &ctx->response_buf_size, response_need,
27
+ NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_RESPONSE_BUF_REALLOC_INTERNAL))
28
+ return false;
29
+
30
+ if (ctx->session.selected_profile == NIPC_PROFILE_SHM_HYBRID ||
31
+ ctx->session.selected_profile == NIPC_PROFILE_SHM_FUTEX) {
32
+ size_t send_need;
33
+ if (!nipc_service_common_header_payload_len(
34
+ ctx->session.max_request_payload_bytes, &send_need))
35
+ return false;
36
+ if (!nipc_service_posix_ensure_buffer(&ctx->send_buf, &ctx->send_buf_size, send_need,
37
+ NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL))
38
+ return false;
39
+ }
40
+
41
+ return true;
42
+}
43
+
44
+/* ------------------------------------------------------------------ */
45
+/* Internal: client connection helpers */
46
+/* ------------------------------------------------------------------ */
47
+
48
+/* Tear down the current connection (UDS session + SHM if any). */
49
+void nipc_service_posix_client_disconnect(nipc_client_ctx_t *ctx)
50
+{
51
+ if (ctx->shm) {
52
+ nipc_shm_close(ctx->shm);
53
+ free(ctx->shm);
54
+ ctx->shm = NULL;
55
+ }
56
+
57
+ if (ctx->session_valid) {
58
+ nipc_uds_close_session(&ctx->session);
59
+ ctx->session_valid = false;
60
+ }
61
+}
62
+
63
+static void client_disable_shm_profiles(nipc_client_ctx_t *ctx)
64
+{
65
+ ctx->transport_config.supported_profiles &=
66
+ ~(NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
67
+ ctx->transport_config.preferred_profiles &=
68
+ ~(NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
69
+}
70
+
71
+/* Attempt a full connection: UDS connect + handshake, then SHM upgrade
72
+ * if negotiated. Returns the new state. */
73
+nipc_client_state_t nipc_service_posix_client_try_connect(nipc_client_ctx_t *ctx)
74
+{
75
+ nipc_uds_session_t session;
76
+ memset(&session, 0, sizeof(session));
77
+ session.fd = -1;
78
+
79
+ nipc_uds_error_t err = nipc_uds_connect(
80
+ ctx->run_dir, ctx->service_name,
81
+ &ctx->transport_config, &session);
82
+
83
+ switch (err) {
84
+ case NIPC_UDS_OK:
85
+ break;
86
+ case NIPC_UDS_ERR_CONNECT:
87
+ return NIPC_CLIENT_NOT_FOUND;
88
+ case NIPC_UDS_ERR_AUTH_FAILED:
89
+ return NIPC_CLIENT_AUTH_FAILED;
90
+ case NIPC_UDS_ERR_NO_PROFILE:
91
+ case NIPC_UDS_ERR_INCOMPATIBLE:
92
+ return NIPC_CLIENT_INCOMPATIBLE;
93
+ default:
94
+ return NIPC_CLIENT_DISCONNECTED;
95
+ }
96
+
97
+ ctx->session = session;
98
+ ctx->session_valid = true;
99
+
100
+ if (!client_prepare_session_buffers(ctx)) {
101
+ nipc_uds_close_session(&ctx->session);
102
+ ctx->session_valid = false;
103
+ return NIPC_CLIENT_DISCONNECTED;
104
+ }
105
+
106
+ /* SHM upgrade if negotiated */
107
+ if (session.selected_profile == NIPC_PROFILE_SHM_HYBRID ||
108
+ session.selected_profile == NIPC_PROFILE_SHM_FUTEX) {
109
+
110
+ nipc_shm_ctx_t *shm = nipc_service_posix_calloc(
111
+ 1, sizeof(nipc_shm_ctx_t),
112
+ NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SHM_CTX_CALLOC_INTERNAL);
113
+ if (!shm) {
114
+ nipc_uds_close_session(&ctx->session);
115
+ ctx->session_valid = false;
116
+ return NIPC_CLIENT_DISCONNECTED;
117
+ }
118
+ {
119
+ /* Retry attach: server creates the SHM region after
120
+ * the UDS handshake, so it may not exist yet. */
121
+ nipc_shm_error_t serr = NIPC_SHM_ERR_NOT_READY;
122
+ uint64_t deadline_ms = nipc_service_platform_monotonic_ms() + CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS;
123
+ for (;;) {
124
+ serr = nipc_shm_client_attach(
125
+ ctx->run_dir, ctx->service_name,
126
+ session.session_id, shm);
127
+ if (serr == NIPC_SHM_OK)
128
+ break;
129
+ if (serr != NIPC_SHM_ERR_NOT_READY &&
130
+ serr != NIPC_SHM_ERR_OPEN &&
131
+ serr != NIPC_SHM_ERR_BAD_MAGIC)
132
+ break;
133
+ if (nipc_service_platform_monotonic_ms() >= deadline_ms)
134
+ break;
135
+ nipc_service_posix_sleep_us(CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS * 1000u);
136
+ }
137
+
138
+ if (serr == NIPC_SHM_OK) {
139
+ ctx->shm = shm;
140
+ } else {
141
+ /* SHM attach failed after negotiation. Close that session,
142
+ * blacklist SHM for this client context, and retry
143
+ * baseline via a new handshake. */
144
+ free(shm);
145
+ nipc_uds_close_session(&ctx->session);
146
+ ctx->session_valid = false;
147
+ client_disable_shm_profiles(ctx);
148
+ if (ctx->transport_config.supported_profiles == 0)
149
+ return NIPC_CLIENT_DISCONNECTED;
150
+ return nipc_service_posix_client_try_connect(ctx);
151
+ }
152
+ }
153
+ }
154
+
155
+ return NIPC_CLIENT_READY;
156
+}
157
+
158
+bool nipc_service_posix_client_reconnect_for_call(nipc_client_ctx_t *ctx)
159
+{
160
+ for (uint32_t i = 0; i < CLIENT_CALL_RECONNECT_RETRIES; i++) {
161
+ ctx->state = nipc_service_posix_client_try_connect(ctx);
162
+ if (ctx->state == NIPC_CLIENT_READY)
163
+ return true;
164
+ if (ctx->state == NIPC_CLIENT_AUTH_FAILED ||
165
+ ctx->state == NIPC_CLIENT_INCOMPATIBLE)
166
+ return false;
167
+ if (i + 1u < CLIENT_CALL_RECONNECT_RETRIES)
168
+ nipc_service_posix_sleep_us(CLIENT_CALL_RECONNECT_RETRY_INTERVAL_MS * 1000u);
169
+ }
170
+
171
+ return false;
172
+}
src/libnetdata/netipc/src/service/netipc_service_posix_internal.h
new
+51
@@ -0,0 +1,51 @@
1
+#ifndef NETIPC_SERVICE_POSIX_INTERNAL_H
2
+#define NETIPC_SERVICE_POSIX_INTERNAL_H
3
+
4
+#include "netipc_service_platform.h"
5
+
6
+#include <pthread.h>
7
+#include <stdbool.h>
8
+#include <stddef.h>
9
+#include <stdint.h>
10
+
11
+#define SERVER_POLL_TIMEOUT_MS 100
12
+#define CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS 5u
13
+#define CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS 5000u
14
+#define CLIENT_CALL_RECONNECT_RETRY_INTERVAL_MS 5u
15
+#define CLIENT_CALL_RECONNECT_DRAIN_MS (SERVER_POLL_TIMEOUT_MS + 50u)
16
+#define CLIENT_CALL_RECONNECT_RETRIES 20u
17
+
18
+enum {
19
+ NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_RESPONSE_BUF_REALLOC_INTERNAL = 1,
20
+ NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL,
21
+ NIPC_POSIX_SERVICE_TEST_FAULT_CLIENT_SHM_CTX_CALLOC_INTERNAL,
22
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SHM_CTX_CALLOC_INTERNAL,
23
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RECV_BUF_MALLOC_INTERNAL,
24
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL,
25
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SESSIONS_CALLOC_INTERNAL,
26
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SESSION_CTX_CALLOC_INTERNAL,
27
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_THREAD_CREATE_INTERNAL,
28
+ NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_BUCKETS_CALLOC_INTERNAL,
29
+ NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEMS_CALLOC_INTERNAL,
30
+ NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEM_NAME_MALLOC_INTERNAL,
31
+ NIPC_POSIX_SERVICE_TEST_FAULT_CACHE_ITEM_PATH_MALLOC_INTERNAL,
32
+};
33
+
34
+void nipc_service_posix_sleep_us(unsigned int usec);
35
+const nipc_service_common_client_ops_t *nipc_service_posix_client_ops(void);
36
+void nipc_service_posix_client_disconnect(nipc_client_ctx_t *ctx);
37
+nipc_client_state_t nipc_service_posix_client_try_connect(nipc_client_ctx_t *ctx);
38
+bool nipc_service_posix_client_reconnect_for_call(nipc_client_ctx_t *ctx);
39
+void *nipc_service_posix_malloc(size_t size, int fault_site);
40
+void *nipc_service_posix_calloc(size_t count, size_t size, int fault_site);
41
+bool nipc_service_posix_ensure_buffer(uint8_t **buf, size_t *buf_size,
42
+ size_t need, int fault_site);
43
+int nipc_service_posix_poll_with_shutdown(int fd, bool *running);
44
+void *nipc_service_posix_session_handler_thread(void *arg);
45
+void nipc_service_posix_server_reap_sessions_locked(nipc_managed_server_t *server);
46
+int nipc_service_posix_pthread_create(pthread_t *thread,
47
+ const pthread_attr_t *attr,
48
+ void *(*start_routine)(void *),
49
+ void *arg);
50
+
51
+#endif /* NETIPC_SERVICE_POSIX_INTERNAL_H */
src/libnetdata/netipc/src/service/netipc_service_posix_server.c
new
+385
@@ -0,0 +1,385 @@
1
+/*
2
+ * netipc_service_posix_server.c - POSIX managed server orchestration.
3
+ */
4
+
5
+#include "netipc/netipc_service.h"
6
+#include "netipc/netipc_protocol.h"
7
+#include "netipc/netipc_uds.h"
8
+#include "netipc/netipc_shm.h"
9
+#include "netipc_service_common.h"
10
+#include "netipc_service_platform.h"
11
+#include "netipc_service_posix_internal.h"
12
+
13
+#include <errno.h>
14
+#include <poll.h>
15
+#include <stdint.h>
16
+#include <stdlib.h>
17
+#include <string.h>
18
+#include <sys/socket.h>
19
+#include <time.h>
20
+#include <unistd.h>
21
+
22
+/* ------------------------------------------------------------------ */
23
+/* Internal: managed server session handler */
24
+/* ------------------------------------------------------------------ */
25
+
26
+/*
27
+ * Wait for data on a file descriptor with periodic shutdown checks.
28
+ * Returns: 1 = data ready, 0 = server stopping, -1 = error/hangup.
29
+ */
30
+static void server_destroy_precreated_shm(nipc_shm_ctx_t **shm)
31
+{
32
+ if (!shm || !*shm)
33
+ return;
34
+ nipc_shm_destroy(*shm);
35
+ free(*shm);
36
+ *shm = NULL;
37
+}
38
+
39
+static bool server_prepare_accept_config(nipc_managed_server_t *server,
40
+ uint64_t sid,
41
+ nipc_uds_server_config_t *cfg_out,
42
+ nipc_shm_ctx_t **shm_out)
43
+{
44
+ *cfg_out = server->base_config;
45
+ cfg_out->max_request_payload_bytes =
46
+ __atomic_load_n(&server->learned_request_payload_bytes, __ATOMIC_ACQUIRE);
47
+ cfg_out->max_response_payload_bytes =
48
+ __atomic_load_n(&server->learned_response_payload_bytes, __ATOMIC_ACQUIRE);
49
+ *shm_out = NULL;
50
+
51
+ uint32_t shm_profiles = cfg_out->supported_profiles &
52
+ (NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
53
+ if (shm_profiles == 0)
54
+ return true;
55
+
56
+ uint32_t request_capacity;
57
+ uint32_t response_capacity;
58
+ if (!nipc_service_common_header_payload_len_u32(
59
+ NIPC_MAX_PAYLOAD_CAP, &request_capacity) ||
60
+ !nipc_service_common_header_payload_len_u32(
61
+ cfg_out->max_response_payload_bytes, &response_capacity)) {
62
+ cfg_out->supported_profiles &= ~shm_profiles;
63
+ cfg_out->preferred_profiles &= ~shm_profiles;
64
+ return cfg_out->supported_profiles != 0;
65
+ }
66
+
67
+ nipc_shm_ctx_t *shm = nipc_service_posix_calloc(
68
+ 1, sizeof(nipc_shm_ctx_t),
69
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SHM_CTX_CALLOC_INTERNAL);
70
+ if (!shm)
71
+ return false;
72
+
73
+ /* HELLO has not been read yet, so the request segment must cover any
74
+ * client proposal the handshake may legally echo back. */
75
+ nipc_shm_error_t serr = nipc_shm_server_create(
76
+ server->run_dir, server->service_name,
77
+ sid,
78
+ request_capacity,
79
+ response_capacity,
80
+ shm);
81
+ if (serr == NIPC_SHM_OK) {
82
+ *shm_out = shm;
83
+ return true;
84
+ }
85
+
86
+ free(shm);
87
+ cfg_out->supported_profiles &= ~(NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
88
+ cfg_out->preferred_profiles &= ~(NIPC_PROFILE_SHM_HYBRID | NIPC_PROFILE_SHM_FUTEX);
89
+ return cfg_out->supported_profiles != 0;
90
+}
91
+
92
+/* ------------------------------------------------------------------ */
93
+/* Public API: managed server */
94
+/* ------------------------------------------------------------------ */
95
+
96
+nipc_error_t nipc_service_platform_server_init_raw(
97
+ nipc_managed_server_t *server,
98
+ const char *run_dir,
99
+ const char *service_name,
100
+ const nipc_service_platform_server_config_t *config,
101
+ int worker_count,
102
+ uint16_t expected_method_code,
103
+ nipc_server_handler_fn handler,
104
+ void *user)
105
+{
106
+ if (!server)
107
+ return NIPC_ERR_BAD_LAYOUT;
108
+
109
+ memset(server, 0, sizeof(*server));
110
+ server->listener.fd = -1;
111
+ __atomic_store_n(&server->running, false, __ATOMIC_RELAXED);
112
+ server->acceptor_started = false;
113
+
114
+ if (!config)
115
+ return NIPC_ERR_BAD_LAYOUT;
116
+
117
+ nipc_error_t ierr = nipc_service_common_server_init_base(
118
+ server, run_dir, service_name, worker_count, expected_method_code,
119
+ handler, user, config->max_request_payload_bytes,
120
+ config->max_response_payload_bytes);
121
+ if (ierr != NIPC_OK)
122
+ return ierr;
123
+ server->base_config = *config;
124
+ ierr = nipc_service_common_server_alloc_sessions(
125
+ server, nipc_service_posix_calloc,
126
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SESSIONS_CALLOC_INTERNAL);
127
+ if (ierr != NIPC_OK)
128
+ return ierr;
129
+ pthread_mutex_init(&server->sessions_lock, NULL);
130
+
131
+ /* Clean up stale SHM regions from previous crashes (spec requirement:
132
+ * runs once at server startup, before the listener begins accepting). */
133
+ nipc_shm_cleanup_stale(run_dir, service_name);
134
+
135
+ /* Start listening via L1 */
136
+ nipc_uds_error_t uerr = nipc_uds_listen(
137
+ run_dir, service_name, config, &server->listener);
138
+ if (uerr != NIPC_UDS_OK) {
139
+ free(server->sessions);
140
+ server->sessions = NULL;
141
+ pthread_mutex_destroy(&server->sessions_lock);
142
+ return NIPC_ERR_BAD_LAYOUT;
143
+ }
144
+
145
+ return NIPC_OK;
146
+}
147
+
148
+nipc_error_t nipc_server_init_raw_for_tests(nipc_managed_server_t *server,
149
+ const char *run_dir,
150
+ const char *service_name,
151
+ const nipc_uds_server_config_t *config,
152
+ int worker_count,
153
+ uint16_t expected_method_code,
154
+ nipc_server_handler_fn handler,
155
+ void *user)
156
+{
157
+ return nipc_service_platform_server_init_raw(
158
+ server, run_dir, service_name, config,
159
+ worker_count, expected_method_code, handler, user);
160
+}
161
+
162
+void nipc_server_run(nipc_managed_server_t *server)
163
+{
164
+ __atomic_store_n(&server->running, true, __ATOMIC_RELEASE);
165
+
166
+ while (__atomic_load_n(&server->running, __ATOMIC_RELAXED)) {
167
+ /* Poll the listener fd before blocking on accept */
168
+ int pr = nipc_service_posix_poll_with_shutdown(server->listener.fd, &server->running);
169
+ if (pr <= 0)
170
+ break; /* shutdown or error */
171
+
172
+ /* Accept one client via L1 */
173
+ nipc_uds_session_t session;
174
+ memset(&session, 0, sizeof(session));
175
+ session.fd = -1;
176
+
177
+ uint64_t sid = server->next_session_id++;
178
+ nipc_uds_server_config_t accept_cfg;
179
+ nipc_shm_ctx_t *prepared_shm = NULL;
180
+ if (!server_prepare_accept_config(server, sid, &accept_cfg, &prepared_shm)) {
181
+ nipc_service_posix_sleep_us(10000);
182
+ continue;
183
+ }
184
+
185
+ server->listener.config = accept_cfg;
186
+ nipc_uds_error_t uerr = nipc_uds_accept(
187
+ &server->listener, sid, &session);
188
+ if (uerr != NIPC_UDS_OK) {
189
+ server_destroy_precreated_shm(&prepared_shm);
190
+ if (!__atomic_load_n(&server->running, __ATOMIC_RELAXED))
191
+ break;
192
+ nipc_service_posix_sleep_us(10000);
193
+ continue;
194
+ }
195
+
196
+ nipc_service_common_server_note_request_capacity(
197
+ server, session.max_request_payload_bytes);
198
+ nipc_service_common_server_note_response_capacity(
199
+ server, session.max_response_payload_bytes);
200
+
201
+ /* Enforce worker_count limit: reap finished sessions, check count */
202
+ pthread_mutex_lock(&server->sessions_lock);
203
+ nipc_service_posix_server_reap_sessions_locked(server);
204
+
205
+ if (server->session_count >= server->worker_count) {
206
+ /* At capacity: reject this client by closing the session */
207
+ pthread_mutex_unlock(&server->sessions_lock);
208
+ server_destroy_precreated_shm(&prepared_shm);
209
+ nipc_uds_close_session(&session);
210
+ continue;
211
+ }
212
+
213
+ /* SHM profile guarantee: only negotiate SHM for sessions that already
214
+ * have a prepared per-session SHM region. */
215
+ nipc_shm_ctx_t *shm = prepared_shm;
216
+ if (session.selected_profile == NIPC_PROFILE_SHM_HYBRID ||
217
+ session.selected_profile == NIPC_PROFILE_SHM_FUTEX) {
218
+ if (!shm) {
219
+ pthread_mutex_unlock(&server->sessions_lock);
220
+ nipc_uds_close_session(&session);
221
+ continue;
222
+ }
223
+ } else {
224
+ server_destroy_precreated_shm(&prepared_shm);
225
+ shm = NULL;
226
+ }
227
+
228
+ /* Create session context */
229
+ nipc_session_ctx_t *sctx = nipc_service_posix_calloc(
230
+ 1, sizeof(nipc_session_ctx_t),
231
+ NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_SESSION_CTX_CALLOC_INTERNAL);
232
+ if (!sctx) {
233
+ if (shm) { nipc_shm_destroy(shm); free(shm); }
234
+ pthread_mutex_unlock(&server->sessions_lock);
235
+ nipc_uds_close_session(&session);
236
+ continue;
237
+ }
238
+
239
+ /* The caller-owned server object stays live until destroy joins sessions. */
240
+// codeql[cpp/stack-address-escape]
241
+ sctx->server = server;
242
+ sctx->session = session;
243
+ sctx->shm = shm;
244
+ sctx->id = sid;
245
+ __atomic_store_n(&sctx->active, true, __ATOMIC_RELAXED);
246
+
247
+ server->sessions[server->session_count++] = sctx;
248
+ pthread_mutex_unlock(&server->sessions_lock);
249
+
250
+ /* Spawn handler thread for this session */
251
+ int rc = nipc_service_posix_pthread_create(&sctx->thread, NULL,
252
+ nipc_service_posix_session_handler_thread, sctx);
253
+ if (rc != 0) {
254
+ /* Thread creation failed: clean up */
255
+ pthread_mutex_lock(&server->sessions_lock);
256
+ /* Remove the sctx we just added */
257
+ for (int i = 0; i < server->session_count; i++) {
258
+ if (server->sessions[i] == sctx) {
259
+ server->sessions[i] = server->sessions[server->session_count - 1];
260
+ server->session_count--;
261
+ break;
262
+ }
263
+ }
264
+ pthread_mutex_unlock(&server->sessions_lock);
265
+
266
+ if (shm) { nipc_shm_destroy(shm); free(shm); }
267
+ nipc_uds_close_session(&session);
268
+ free(sctx);
269
+ }
270
+ }
271
+}
272
+
273
+void nipc_server_stop(nipc_managed_server_t *server)
274
+{
275
+ __atomic_store_n(&server->running, false, __ATOMIC_RELEASE);
276
+}
277
+
278
+bool nipc_server_drain(nipc_managed_server_t *server, uint32_t timeout_ms)
279
+{
280
+ /* 1. Stop accepting new clients.
281
+ * Do NOT close the listener here — the run loop may still be
282
+ * polling on listener.fd. Setting the flag is enough; the run
283
+ * loop will exit on its next poll timeout (100ms). The listener
284
+ * is closed later by nipc_server_destroy(). */
285
+ __atomic_store_n(&server->running, false, __ATOMIC_RELEASE);
286
+
287
+ /* 2. Wait for in-flight sessions to complete */
288
+ bool all_drained = true;
289
+ if (server->sessions) {
290
+ struct timespec deadline;
291
+ clock_gettime(CLOCK_MONOTONIC, &deadline);
292
+ deadline.tv_sec += timeout_ms / 1000;
293
+ deadline.tv_nsec += (timeout_ms % 1000) * 1000000L;
294
+ if (deadline.tv_nsec >= 1000000000L) {
295
+ deadline.tv_sec++;
296
+ deadline.tv_nsec -= 1000000000L;
297
+ }
298
+
299
+ /* Poll until all sessions are inactive or timeout */
300
+ while (1) {
301
+ pthread_mutex_lock(&server->sessions_lock);
302
+ int active_count = 0;
303
+ for (int i = 0; i < server->session_count; i++) {
304
+ if (__atomic_load_n(&server->sessions[i]->active,
305
+ __ATOMIC_ACQUIRE))
306
+ active_count++;
307
+ }
308
+ pthread_mutex_unlock(&server->sessions_lock);
309
+
310
+ if (active_count == 0)
311
+ break;
312
+
313
+ struct timespec now;
314
+ clock_gettime(CLOCK_MONOTONIC, &now);
315
+ if (now.tv_sec > deadline.tv_sec ||
316
+ (now.tv_sec == deadline.tv_sec &&
317
+ now.tv_nsec >= deadline.tv_nsec)) {
318
+ /* Timeout: force-close session fds to unblock recv.
319
+ * Closing the fd causes poll/recv to return error,
320
+ * which terminates the session handler loop. */
321
+ pthread_mutex_lock(&server->sessions_lock);
322
+ for (int i = 0; i < server->session_count; i++) {
323
+ nipc_session_ctx_t *s = server->sessions[i];
324
+ if (__atomic_load_n(&s->active, __ATOMIC_ACQUIRE)) {
325
+ if (s->session.fd >= 0) {
326
+ shutdown(s->session.fd, SHUT_RDWR);
327
+ }
328
+ }
329
+ }
330
+ pthread_mutex_unlock(&server->sessions_lock);
331
+ all_drained = false;
332
+ break;
333
+ }
334
+
335
+ nipc_service_posix_sleep_us(5000); /* 5ms poll interval */
336
+ }
337
+
338
+ /* 3. Join all session threads (finished or not) */
339
+ pthread_mutex_lock(&server->sessions_lock);
340
+ for (int i = 0; i < server->session_count; i++) {
341
+ nipc_session_ctx_t *s = server->sessions[i];
342
+ pthread_mutex_unlock(&server->sessions_lock);
343
+ pthread_join(s->thread, NULL);
344
+ free(s);
345
+ pthread_mutex_lock(&server->sessions_lock);
346
+ }
347
+ server->session_count = 0;
348
+ pthread_mutex_unlock(&server->sessions_lock);
349
+
350
+ free(server->sessions);
351
+ server->sessions = NULL;
352
+ server->session_capacity = 0;
353
+ pthread_mutex_destroy(&server->sessions_lock);
354
+ }
355
+
356
+ server->worker_count = 0;
357
+ return all_drained;
358
+}
359
+
360
+void nipc_server_destroy(nipc_managed_server_t *server)
361
+{
362
+ __atomic_store_n(&server->running, false, __ATOMIC_RELEASE);
363
+ nipc_uds_close_listener(&server->listener);
364
+
365
+ /* Join all active session threads */
366
+ if (server->sessions) {
367
+ pthread_mutex_lock(&server->sessions_lock);
368
+ for (int i = 0; i < server->session_count; i++) {
369
+ nipc_session_ctx_t *s = server->sessions[i];
370
+ pthread_mutex_unlock(&server->sessions_lock);
371
+ pthread_join(s->thread, NULL);
372
+ free(s);
373
+ pthread_mutex_lock(&server->sessions_lock);
374
+ }
375
+ server->session_count = 0;
376
+ pthread_mutex_unlock(&server->sessions_lock);
377
+
378
+ free(server->sessions);
379
+ server->sessions = NULL;
380
+ server->session_capacity = 0;
381
+ pthread_mutex_destroy(&server->sessions_lock);
382
+ }
383
+
384
+ server->worker_count = 0;
385
+}
src/libnetdata/netipc/src/service/netipc_service_posix_server_session.c
new
+319
@@ -0,0 +1,319 @@
1
+/*
2
+ * netipc_service_posix_server_session.c - POSIX managed server session loop.
3
+ */
4
+
5
+#include "netipc/netipc_service.h"
6
+#include "netipc/netipc_protocol.h"
7
+#include "netipc/netipc_uds.h"
8
+#include "netipc/netipc_shm.h"
9
+#include "netipc_service_common.h"
10
+#include "netipc_service_posix_internal.h"
11
+
12
+#include <errno.h>
13
+#include <poll.h>
14
+#include <stdint.h>
15
+#include <stdlib.h>
16
+#include <string.h>
17
+#include <sys/socket.h>
18
+
19
+int nipc_service_posix_poll_with_shutdown(int fd, bool *running)
20
+{
21
+ while (__atomic_load_n(running, __ATOMIC_RELAXED)) {
22
+ struct pollfd pfd = { .fd = fd, .events = POLLIN };
23
+ int ret = poll(&pfd, 1, SERVER_POLL_TIMEOUT_MS);
24
+
25
+ if (ret < 0) {
26
+ if (errno == EINTR)
27
+ continue;
28
+ return -1;
29
+ }
30
+
31
+ if (ret == 0)
32
+ continue; /* timeout, check running flag */
33
+
34
+ if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL))
35
+ return -1;
36
+
37
+ if (pfd.revents & POLLIN)
38
+ return 1;
39
+ }
40
+ return 0;
41
+}
42
+
43
+static bool session_peer_disconnected(int fd)
44
+{
45
+ if (fd < 0)
46
+ return true;
47
+
48
+ struct pollfd pfd = {
49
+ .fd = fd,
50
+ .events = POLLIN | POLLHUP | POLLERR,
51
+ };
52
+#ifdef POLLRDHUP
53
+ pfd.events |= POLLRDHUP;
54
+#endif
55
+
56
+ int ret;
57
+ do {
58
+ ret = poll(&pfd, 1, 0);
59
+ } while (ret < 0 && errno == EINTR);
60
+
61
+ if (ret < 0)
62
+ return true;
63
+ if (ret == 0)
64
+ return false;
65
+
66
+ short disconnected = POLLHUP | POLLERR | POLLNVAL;
67
+#ifdef POLLRDHUP
68
+ disconnected |= POLLRDHUP;
69
+#endif
70
+ if (pfd.revents & disconnected)
71
+ return true;
72
+
73
+ if (pfd.revents & POLLIN) {
74
+ char ch;
75
+ ssize_t n;
76
+ do {
77
+ n = recv(fd, &ch, sizeof(ch), MSG_PEEK | MSG_DONTWAIT);
78
+ } while (n < 0 && errno == EINTR);
79
+
80
+ if (n == 0)
81
+ return true;
82
+ if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
83
+ return true;
84
+ }
85
+
86
+ return false;
87
+}
88
+
89
+/*
90
+ * Handle one client session: read requests, dispatch to handler,
91
+ * send responses. Each session gets its own response buffer.
92
+ * Runs until the client disconnects or server stops.
93
+ */
94
+static void server_handle_session(nipc_managed_server_t *server,
95
+ nipc_uds_session_t *session,
96
+ nipc_shm_ctx_t *shm,
97
+ uint8_t *resp_buf,
98
+ size_t resp_buf_size)
99
+{
100
+ /* Allocate recv buffer based on negotiated max request size */
101
+ size_t recv_size;
102
+ if (!nipc_service_common_header_payload_len(
103
+ session->max_request_payload_bytes, &recv_size))
104
+ return;
105
+ if (recv_size < NIPC_HEADER_LEN + 1024u)
106
+ recv_size = NIPC_HEADER_LEN + 1024u;
107
+ uint8_t *recv_buf = nipc_service_posix_malloc(
108
+ recv_size, NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RECV_BUF_MALLOC_INTERNAL);
109
+ if (!recv_buf)
110
+ return;
111
+
112
+ while (__atomic_load_n(&server->running, __ATOMIC_RELAXED)) {
113
+ nipc_header_t hdr;
114
+ const void *payload;
115
+ size_t payload_len;
116
+
117
+ /* Receive request via the active transport */
118
+ if (shm) {
119
+ size_t msg_len;
120
+ nipc_shm_error_t serr = nipc_shm_receive(shm, recv_buf, recv_size,
121
+ &msg_len, SERVER_POLL_TIMEOUT_MS);
122
+ if (serr == NIPC_SHM_ERR_TIMEOUT) {
123
+ if (session_peer_disconnected(session->fd))
124
+ break;
125
+ continue; /* check running flag */
126
+ }
127
+ if (serr != NIPC_SHM_OK)
128
+ break;
129
+ if (msg_len < NIPC_HEADER_LEN)
130
+ break;
131
+
132
+ nipc_error_t perr = nipc_header_decode(recv_buf, msg_len, &hdr);
133
+ if (perr != NIPC_OK)
134
+ break;
135
+
136
+ payload = recv_buf + NIPC_HEADER_LEN;
137
+ payload_len = msg_len - NIPC_HEADER_LEN;
138
+ } else {
139
+ /* Poll the session fd before blocking on receive */
140
+ int pr = nipc_service_posix_poll_with_shutdown(session->fd, &server->running);
141
+ if (pr <= 0)
142
+ break; /* shutdown or error */
143
+
144
+ nipc_uds_error_t uerr = nipc_uds_receive(
145
+ session, recv_buf, recv_size,
146
+ &hdr, &payload, &payload_len);
147
+ if (uerr == NIPC_UDS_ERR_LIMIT_EXCEEDED) {
148
+ if (hdr.kind == NIPC_KIND_REQUEST) {
149
+ if (hdr.payload_len > 0)
150
+ nipc_service_common_server_note_request_capacity(
151
+ server, hdr.payload_len);
152
+
153
+ nipc_header_t resp_hdr = {0};
154
+ resp_hdr.kind = NIPC_KIND_RESPONSE;
155
+ resp_hdr.code = hdr.code;
156
+ resp_hdr.message_id = hdr.message_id;
157
+ resp_hdr.transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
158
+ resp_hdr.item_count = 1;
159
+ resp_hdr.flags = 0;
160
+
161
+ if (nipc_uds_send(session, &resp_hdr, NULL, 0) != NIPC_UDS_OK)
162
+ break;
163
+ }
164
+ break;
165
+ }
166
+ if (uerr != NIPC_UDS_OK)
167
+ break;
168
+ }
169
+
170
+ /* Protocol violation: unexpected message kind terminates session */
171
+ if (hdr.kind != NIPC_KIND_REQUEST)
172
+ break;
173
+
174
+ if (hdr.code != server->expected_method_code) {
175
+ nipc_header_t resp_hdr = {0};
176
+ resp_hdr.kind = NIPC_KIND_RESPONSE;
177
+ resp_hdr.code = hdr.code;
178
+ resp_hdr.message_id = hdr.message_id;
179
+ resp_hdr.transport_status = NIPC_STATUS_UNSUPPORTED;
180
+ resp_hdr.item_count = 1;
181
+ resp_hdr.flags = 0;
182
+
183
+ if (shm) {
184
+ uint8_t msg[NIPC_HEADER_LEN];
185
+ resp_hdr.magic = NIPC_MAGIC_MSG;
186
+ resp_hdr.version = NIPC_VERSION;
187
+ resp_hdr.header_len = NIPC_HEADER_LEN;
188
+ resp_hdr.payload_len = 0;
189
+ nipc_header_encode(&resp_hdr, msg, sizeof(msg));
190
+ if (nipc_shm_send(shm, msg, sizeof(msg)) != NIPC_SHM_OK)
191
+ break;
192
+ } else {
193
+ if (nipc_uds_send(session, &resp_hdr, NULL, 0) != NIPC_UDS_OK)
194
+ break;
195
+ }
196
+ continue;
197
+ }
198
+
199
+ if (payload_len <= UINT32_MAX)
200
+ nipc_service_common_server_note_request_capacity(
201
+ server, (uint32_t)payload_len);
202
+
203
+ /* Dispatch: one request kind per service endpoint. */
204
+ size_t response_len = 0;
205
+ nipc_error_t dispatch_err = server->handler(
206
+ server->handler_user,
207
+ &hdr,
208
+ (const uint8_t *)payload, payload_len,
209
+ resp_buf, resp_buf_size,
210
+ &response_len);
211
+
212
+ /* Build response header */
213
+ nipc_header_t resp_hdr;
214
+ bool close_after_response = false;
215
+ nipc_service_common_prepare_response_header(&hdr, &resp_hdr);
216
+ nipc_service_common_apply_dispatch_result(
217
+ server, dispatch_err, resp_buf_size,
218
+ session->max_response_payload_bytes, false,
219
+ &resp_hdr, &response_len, &close_after_response);
220
+
221
+ /* Send response via the active transport */
222
+ if (shm) {
223
+ size_t msg_len;
224
+ if (!nipc_service_common_header_payload_len(response_len, &msg_len))
225
+ break;
226
+
227
+ resp_hdr.magic = NIPC_MAGIC_MSG;
228
+ resp_hdr.version = NIPC_VERSION;
229
+ resp_hdr.header_len = NIPC_HEADER_LEN;
230
+ resp_hdr.payload_len = (uint32_t)response_len;
231
+
232
+ /* Use a stack buffer for small responses, heap for large ones */
233
+ uint8_t stack_msg[4096];
234
+ uint8_t *msg = (msg_len <= sizeof(stack_msg)) ? stack_msg :
235
+ nipc_service_posix_malloc(msg_len, NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL);
236
+ if (!msg)
237
+ break;
238
+
239
+ nipc_header_encode(&resp_hdr, msg, NIPC_HEADER_LEN);
240
+ if (response_len > 0)
241
+ memcpy(msg + NIPC_HEADER_LEN, resp_buf, response_len);
242
+
243
+ nipc_shm_error_t serr = nipc_shm_send(shm, msg, msg_len);
244
+ if (msg != stack_msg)
245
+ free(msg);
246
+ if (serr != NIPC_SHM_OK)
247
+ break;
248
+ } else {
249
+ nipc_uds_error_t uerr = nipc_uds_send(
250
+ session, &resp_hdr, resp_buf, response_len);
251
+ if (uerr != NIPC_UDS_OK)
252
+ break;
253
+ }
254
+
255
+ if (close_after_response)
256
+ break;
257
+ }
258
+
259
+ free(recv_buf);
260
+}
261
+
262
+/* ------------------------------------------------------------------ */
263
+/* Internal: per-session handler thread */
264
+/* ------------------------------------------------------------------ */
265
+
266
+/* Thread function: handles one client session from accept to disconnect. */
267
+void *nipc_service_posix_session_handler_thread(void *arg)
268
+{
269
+ nipc_session_ctx_t *sctx = (nipc_session_ctx_t *)arg;
270
+ nipc_managed_server_t *server = sctx->server;
271
+
272
+ /* Allocate a per-session response buffer */
273
+ size_t resp_size = (size_t)sctx->session.max_response_payload_bytes;
274
+ if (resp_size < 1024u)
275
+ resp_size = 1024u;
276
+ uint8_t *resp_buf = nipc_service_posix_malloc(
277
+ resp_size, NIPC_POSIX_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL);
278
+ if (resp_buf) {
279
+ server_handle_session(server, &sctx->session, sctx->shm,
280
+ resp_buf, resp_size);
281
+ free(resp_buf);
282
+ }
283
+
284
+ /* Cleanup SHM and session */
285
+ if (sctx->shm) {
286
+ nipc_shm_destroy(sctx->shm);
287
+ free(sctx->shm);
288
+ }
289
+ nipc_uds_close_session(&sctx->session);
290
+
291
+ /* Mark inactive so the acceptor's reap loop (or server destroy)
292
+ * can join this thread and free sctx. Do NOT remove from the
293
+ * tracking array here — the reap/destroy path owns that. */
294
+ __atomic_store_n(&sctx->active, false, __ATOMIC_RELEASE);
295
+ return NULL;
296
+}
297
+
298
+/* ------------------------------------------------------------------ */
299
+/* Internal: reap finished session threads */
300
+/* ------------------------------------------------------------------ */
301
+
302
+/* Reap all finished (inactive) session threads. Called with lock held. */
303
+void nipc_service_posix_server_reap_sessions_locked(nipc_managed_server_t *server)
304
+{
305
+ int i = 0;
306
+ while (i < server->session_count) {
307
+ nipc_session_ctx_t *s = server->sessions[i];
308
+ if (!__atomic_load_n(&s->active, __ATOMIC_ACQUIRE)) {
309
+ pthread_join(s->thread, NULL);
310
+ /* Swap with last, free */
311
+ server->sessions[i] = server->sessions[server->session_count - 1];
312
+ server->session_count--;
313
+ free(s);
314
+ } else {
315
+ i++;
316
+ }
317
+ }
318
+
319
+}
src/libnetdata/netipc/src/service/netipc_service_win.c
+29
-1698
@@ -15,6 +15,9 @@
15
#include "netipc/netipc_protocol.h"
16
#include "netipc/netipc_named_pipe.h"
17
#include "netipc/netipc_win_shm.h"
18
+#include "netipc_service_common.h"
19
+#include "netipc_service_platform.h"
20
+#include "netipc_service_win_internal.h"
21
22
#include <stdint.h>
23
#include <stdlib.h>
@@ -22,27 +25,12 @@
25
#include <process.h>
26
#include <windows.h>
27
25
-/* WaitForSingleObject timeout for server poll loops (ms) */
26
-#define SERVER_POLL_TIMEOUT_MS 100
27
-#define NIPC_CLIENT_BUF_DEFAULT 65536u
28
#define CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS 5u
29
#define CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS 5000u
30
+#define CLIENT_CALL_RECONNECT_RETRY_INTERVAL_MS 5u
31
+#define CLIENT_CALL_RECONNECT_RETRIES 20u
32
+
33
31
-enum {
32
- NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_RESPONSE_BUF_REALLOC_INTERNAL = 1,
33
- NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL,
34
- NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SHM_CTX_CALLOC_INTERNAL,
35
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SHM_CTX_CALLOC_INTERNAL,
36
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_RECV_BUF_MALLOC_INTERNAL,
37
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL,
38
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SESSIONS_CALLOC_INTERNAL,
39
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SESSION_CTX_CALLOC_INTERNAL,
40
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_THREAD_CREATE_INTERNAL,
41
- NIPC_WIN_SERVICE_TEST_FAULT_CACHE_BUCKETS_CALLOC_INTERNAL,
42
- NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEMS_CALLOC_INTERNAL,
43
- NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEM_NAME_MALLOC_INTERNAL,
44
- NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEM_PATH_MALLOC_INTERNAL,
45
-};
34
35
static uint64_t g_win_service_test_fault_state = 0;
36
@@ -87,20 +75,30 @@ static bool service_test_should_fail(int site)
75
}
76
}
77
90
-static void *service_malloc(size_t size, int fault_site)
78
+void *nipc_service_win_malloc(size_t size, int fault_site)
79
{
80
if (service_test_should_fail(fault_site))
81
return NULL;
82
return malloc(size);
83
}
84
97
-static void *service_calloc(size_t count, size_t size, int fault_site)
85
+void *nipc_service_platform_malloc(size_t size, int fault_site)
86
+{
87
+ return nipc_service_win_malloc(size, fault_site);
88
+}
89
+
90
+void *nipc_service_win_calloc(size_t count, size_t size, int fault_site)
91
{
92
if (service_test_should_fail(fault_site))
93
return NULL;
94
return calloc(count, size);
95
}
96
97
+void *nipc_service_platform_calloc(size_t count, size_t size, int fault_site)
98
+{
99
+ return nipc_service_win_calloc(count, size, fault_site);
100
+}
101
+
102
static void *service_realloc(void *ptr, size_t size, int fault_site)
103
{
104
if (service_test_should_fail(fault_site))
@@ -108,7 +106,7 @@ static void *service_realloc(void *ptr, size_t size, int fault_site)
106
return realloc(ptr, size);
107
}
108
111
-static uintptr_t service_beginthreadex(void *security,
109
+uintptr_t nipc_service_win_beginthreadex(void *security,
110
unsigned stack_size,
111
unsigned (__stdcall *start_address)(void *),
112
void *arglist,
@@ -136,20 +134,7 @@ static uintptr_t service_beginthreadex(void *security,
134
#endif
135
}
136
139
-static uint32_t next_power_of_2_u32(uint32_t n)
140
-{
141
- if (n < 16)
142
- return 16;
143
- n--;
144
- n |= n >> 1;
145
- n |= n >> 2;
146
- n |= n >> 4;
147
- n |= n >> 8;
148
- n |= n >> 16;
149
- return n + 1;
150
-}
151
-
152
-static bool ensure_buffer(uint8_t **buf, size_t *buf_size, size_t need, int fault_site)
137
+bool nipc_service_win_ensure_buffer(uint8_t **buf, size_t *buf_size, size_t need, int fault_site)
138
{
139
if (*buf && *buf_size >= need)
140
return true;
@@ -163,1677 +148,23 @@ static bool ensure_buffer(uint8_t **buf, size_t *buf_size, size_t need, int faul
148
return true;
149
}
150
166
-static bool header_payload_len(size_t payload_len, size_t *msg_len_out)
167
-{
168
-#if SIZE_MAX <= UINT32_MAX
169
- if (payload_len > SIZE_MAX - NIPC_HEADER_LEN)
170
- return false;
171
-#endif
172
-
173
- *msg_len_out = NIPC_HEADER_LEN + payload_len;
174
- return true;
175
-}
176
-
177
-static bool header_payload_len_u32(uint32_t payload_len, uint32_t *msg_len_out)
178
-{
179
- if (payload_len > UINT32_MAX - NIPC_HEADER_LEN)
180
- return false;
181
-
182
- *msg_len_out = payload_len + NIPC_HEADER_LEN;
183
- return true;
184
-}
185
-
186
-static void client_note_request_capacity(nipc_client_ctx_t *ctx, uint32_t payload_len)
187
-{
188
- uint32_t grown = next_power_of_2_u32(payload_len);
189
- if (grown > NIPC_MAX_PAYLOAD_CAP)
190
- grown = NIPC_MAX_PAYLOAD_CAP;
191
- if (grown > ctx->transport_config.max_request_payload_bytes)
192
- ctx->transport_config.max_request_payload_bytes = grown;
193
-}
194
-
195
-static void client_note_response_capacity(nipc_client_ctx_t *ctx, uint32_t payload_len)
196
-{
197
- uint32_t grown = next_power_of_2_u32(payload_len);
198
- if (grown > NIPC_MAX_PAYLOAD_CAP)
199
- grown = NIPC_MAX_PAYLOAD_CAP;
200
- if (grown > ctx->transport_config.max_response_payload_bytes)
201
- ctx->transport_config.max_response_payload_bytes = grown;
202
-}
203
-
204
-static bool client_prepare_session_buffers(nipc_client_ctx_t *ctx)
205
-{
206
- size_t response_need;
207
- if (!header_payload_len(ctx->session.max_response_payload_bytes, &response_need))
208
- return false;
209
- if (response_need < NIPC_HEADER_LEN + 1024u)
210
- response_need = NIPC_HEADER_LEN + 1024u;
211
-
212
- if (!ensure_buffer(&ctx->response_buf, &ctx->response_buf_size, response_need,
213
- NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_RESPONSE_BUF_REALLOC_INTERNAL))
214
- return false;
215
-
216
- if (ctx->session.selected_profile == NIPC_WIN_SHM_PROFILE_HYBRID ||
217
- ctx->session.selected_profile == NIPC_WIN_SHM_PROFILE_BUSYWAIT) {
218
- size_t send_need;
219
- if (!header_payload_len(ctx->session.max_request_payload_bytes, &send_need))
220
- return false;
221
- if (!ensure_buffer(&ctx->send_buf, &ctx->send_buf_size, send_need,
222
- NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL))
223
- return false;
224
- }
225
-
226
- return true;
227
-}
228
-
229
-static uint32_t cgroups_request_payload_default(void)
230
-{
231
- return 16u;
232
-}
233
-
234
-static uint32_t cgroups_response_payload_default(void)
235
-{
236
- return NIPC_CLIENT_BUF_DEFAULT;
237
-}
238
-
239
-static nipc_np_client_config_t service_client_config_to_transport(
240
- const nipc_client_config_t *config)
241
-{
242
- nipc_np_client_config_t transport = {0};
243
-
244
- if (!config)
245
- return transport;
246
-
247
- transport.supported_profiles = config->supported_profiles;
248
- transport.preferred_profiles = config->preferred_profiles;
249
- transport.max_request_batch_items = config->max_request_batch_items;
250
- transport.max_response_payload_bytes = config->max_response_payload_bytes;
251
- transport.max_response_batch_items = config->max_request_batch_items;
252
- transport.auth_token = config->auth_token;
253
-
254
- return transport;
255
-}
256
-
257
-static nipc_np_server_config_t service_server_config_to_transport(
258
- const nipc_server_config_t *config)
259
-{
260
- nipc_np_server_config_t transport = {0};
261
-
262
- if (!config)
263
- return transport;
264
-
265
- transport.supported_profiles = config->supported_profiles;
266
- transport.preferred_profiles = config->preferred_profiles;
267
- transport.max_request_batch_items = config->max_request_batch_items;
268
- transport.max_response_payload_bytes = config->max_response_payload_bytes;
269
- transport.max_response_batch_items = config->max_request_batch_items;
270
- transport.auth_token = config->auth_token;
271
-
272
- return transport;
273
-}
274
-
275
-static void server_note_request_capacity(nipc_managed_server_t *server,
276
- uint32_t payload_len);
277
-static void server_note_response_capacity(nipc_managed_server_t *server,
278
- uint32_t payload_len);
279
-
280
-/* ------------------------------------------------------------------ */
281
-/* Internal: client connection helpers */
282
-/* ------------------------------------------------------------------ */
283
-
284
-/* Tear down the current connection (Named Pipe session + Win SHM). */
285
-static void client_disconnect(nipc_client_ctx_t *ctx)
286
-{
287
- if (ctx->shm) {
288
- nipc_win_shm_close(ctx->shm);
289
- free(ctx->shm);
290
- ctx->shm = NULL;
291
- }
292
-
293
- if (ctx->session_valid) {
294
- nipc_np_close_session(&ctx->session);
295
- ctx->session_valid = false;
296
- }
297
-}
298
-
299
-static void client_disable_shm_profiles(nipc_client_ctx_t *ctx)
300
-{
301
- ctx->transport_config.supported_profiles &=
302
- ~(NIPC_WIN_SHM_PROFILE_HYBRID | NIPC_WIN_SHM_PROFILE_BUSYWAIT);
303
- ctx->transport_config.preferred_profiles &=
304
- ~(NIPC_WIN_SHM_PROFILE_HYBRID | NIPC_WIN_SHM_PROFILE_BUSYWAIT);
305
-}
306
-
307
-/* Attempt a full connection: Named Pipe connect + handshake, then
308
- * Win SHM upgrade if negotiated. Returns the new state. */
309
-static nipc_client_state_t client_try_connect(nipc_client_ctx_t *ctx)
310
-{
311
- nipc_np_session_t session;
312
- memset(&session, 0, sizeof(session));
313
- session.pipe = INVALID_HANDLE_VALUE;
314
-
315
- nipc_np_error_t err = nipc_np_connect(
316
- ctx->run_dir, ctx->service_name,
317
- &ctx->transport_config, &session);
318
-
319
- switch (err) {
320
- case NIPC_NP_OK:
321
- break;
322
- case NIPC_NP_ERR_CONNECT:
323
- return NIPC_CLIENT_NOT_FOUND;
324
- case NIPC_NP_ERR_AUTH_FAILED:
325
- return NIPC_CLIENT_AUTH_FAILED;
326
- case NIPC_NP_ERR_NO_PROFILE:
327
- case NIPC_NP_ERR_INCOMPATIBLE:
328
- return NIPC_CLIENT_INCOMPATIBLE;
329
- default:
330
- return NIPC_CLIENT_DISCONNECTED;
331
- }
332
-
333
- ctx->session = session;
334
- ctx->session_valid = true;
335
-
336
- if (!client_prepare_session_buffers(ctx)) {
337
- nipc_np_close_session(&ctx->session);
338
- ctx->session_valid = false;
339
- return NIPC_CLIENT_DISCONNECTED;
340
- }
341
-
342
- /* Win SHM upgrade if negotiated */
343
- if (session.selected_profile == NIPC_WIN_SHM_PROFILE_HYBRID ||
344
- session.selected_profile == NIPC_WIN_SHM_PROFILE_BUSYWAIT) {
345
-
346
- nipc_win_shm_ctx_t *shm = service_calloc(
347
- 1, sizeof(nipc_win_shm_ctx_t),
348
- NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SHM_CTX_CALLOC_INTERNAL);
349
- if (!shm) {
350
- nipc_np_close_session(&ctx->session);
351
- ctx->session_valid = false;
352
- return NIPC_CLIENT_DISCONNECTED;
353
- }
354
- {
355
- /* Retry attach: the server prepares SHM before accepting the
356
- * handshake, but filesystem/object visibility may lag briefly. */
357
- nipc_win_shm_error_t serr = NIPC_WIN_SHM_ERR_OPEN_MAPPING;
358
- ULONGLONG deadline_ms = GetTickCount64() + CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS;
359
- for (;;) {
360
- serr = nipc_win_shm_client_attach(
361
- ctx->run_dir, ctx->service_name,
362
- ctx->transport_config.auth_token,
363
- session.session_id,
364
- session.selected_profile,
365
- shm);
366
- if (serr == NIPC_WIN_SHM_OK)
367
- break;
368
- if (serr != NIPC_WIN_SHM_ERR_OPEN_MAPPING &&
369
- serr != NIPC_WIN_SHM_ERR_OPEN_EVENT &&
370
- serr != NIPC_WIN_SHM_ERR_BAD_MAGIC)
371
- break;
372
- if (GetTickCount64() >= deadline_ms)
373
- break;
374
- Sleep(CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS);
375
- }
376
-
377
- if (serr == NIPC_WIN_SHM_OK) {
378
- ctx->shm = shm;
379
- } else {
380
- /* WinSHM attach failed after negotiation. Close that
381
- * session, blacklist WinSHM for this client context, and
382
- * retry baseline via a new handshake. */
383
- free(shm);
384
- nipc_np_close_session(&ctx->session);
385
- ctx->session_valid = false;
386
- client_disable_shm_profiles(ctx);
387
- if (ctx->transport_config.supported_profiles == 0)
388
- return NIPC_CLIENT_DISCONNECTED;
389
- return client_try_connect(ctx);
390
- }
391
- }
392
- }
393
-
394
- return NIPC_CLIENT_READY;
395
-}
396
-
397
-/* ------------------------------------------------------------------ */
398
-/* Internal: send/receive via the active transport */
399
-/* ------------------------------------------------------------------ */
400
-
401
-static nipc_error_t transport_send(nipc_client_ctx_t *ctx,
402
- nipc_header_t *hdr,
403
- const void *payload,
404
- size_t payload_len)
405
-{
406
- if (payload_len > UINT32_MAX)
407
- return NIPC_ERR_OVERFLOW;
408
-
409
- if (ctx->shm) {
410
- if (payload_len > ctx->session.max_request_payload_bytes) {
411
- client_note_request_capacity(ctx, (uint32_t)payload_len);
412
- return NIPC_ERR_OVERFLOW;
413
- }
414
-
415
- size_t msg_len;
416
- if (!header_payload_len(payload_len, &msg_len))
417
- return NIPC_ERR_OVERFLOW;
418
-
419
- uint8_t *msg = ctx->send_buf;
420
- if (!msg || msg_len > ctx->send_buf_size)
421
- return NIPC_ERR_OVERFLOW;
422
-
423
- hdr->magic = NIPC_MAGIC_MSG;
424
- hdr->version = NIPC_VERSION;
425
- hdr->header_len = NIPC_HEADER_LEN;
426
- hdr->payload_len = (uint32_t)payload_len;
427
-
428
- nipc_header_encode(hdr, msg, NIPC_HEADER_LEN);
429
- if (payload_len > 0)
430
- memcpy(msg + NIPC_HEADER_LEN, payload, payload_len);
431
-
432
- nipc_win_shm_error_t serr = nipc_win_shm_send(ctx->shm, msg, msg_len);
433
- if (serr == NIPC_WIN_SHM_ERR_MSG_TOO_LARGE) {
434
- client_note_request_capacity(ctx, (uint32_t)payload_len);
435
- return NIPC_ERR_OVERFLOW;
436
- }
437
- return (serr == NIPC_WIN_SHM_OK) ? NIPC_OK : NIPC_ERR_NOT_READY;
438
- }
439
-
440
- /* Named Pipe path */
441
- nipc_np_error_t uerr = nipc_np_send(&ctx->session, hdr,
442
- payload, payload_len);
443
- if (uerr == NIPC_NP_ERR_LIMIT_EXCEEDED) {
444
- client_note_request_capacity(ctx, (uint32_t)payload_len);
445
- return NIPC_ERR_OVERFLOW;
446
- }
447
- return (uerr == NIPC_NP_OK) ? NIPC_OK : NIPC_ERR_NOT_READY;
448
-}
449
-
450
-static nipc_error_t transport_receive(nipc_client_ctx_t *ctx,
451
- void *buf, size_t buf_size,
452
- nipc_header_t *hdr_out,
453
- const void **payload_out,
454
- size_t *payload_len_out)
455
-{
456
- if (ctx->shm) {
457
- size_t msg_len;
458
- nipc_win_shm_error_t serr = nipc_win_shm_receive(ctx->shm, buf, buf_size,
459
- &msg_len, 30000);
460
- if (serr != NIPC_WIN_SHM_OK)
461
- return NIPC_ERR_TRUNCATED;
462
-
463
- if (msg_len < NIPC_HEADER_LEN)
464
- return NIPC_ERR_TRUNCATED;
465
-
466
- nipc_error_t perr = nipc_header_decode(buf, msg_len, hdr_out);
467
- if (perr != NIPC_OK)
468
- return perr;
469
-
470
- *payload_out = (const uint8_t *)buf + NIPC_HEADER_LEN;
471
- *payload_len_out = msg_len - NIPC_HEADER_LEN;
472
- return NIPC_OK;
473
- }
474
-
475
- /* Named Pipe path */
476
- nipc_np_error_t uerr = nipc_np_receive(&ctx->session, buf, buf_size,
477
- hdr_out, payload_out,
478
- payload_len_out);
479
- return (uerr == NIPC_NP_OK) ? NIPC_OK : NIPC_ERR_TRUNCATED;
480
-}
481
-
482
-/* ------------------------------------------------------------------ */
483
-/* Internal: generic raw call (send request, receive response) */
484
-/* ------------------------------------------------------------------ */
485
-
486
-/*
487
- * Single-attempt raw call: build envelope, send, receive, validate
488
- * envelope. The caller handles encode before and decode after.
489
- *
490
- * On success, response_payload_out and response_len_out point into the
491
- * internal client response buffer (valid until next call on this context).
492
- */
493
-static nipc_error_t do_raw_call(nipc_client_ctx_t *ctx,
494
- uint16_t method_code,
495
- const void *request_payload,
496
- size_t request_len,
497
- const void **response_payload_out,
498
- size_t *response_len_out)
499
-{
500
- nipc_header_t hdr = {0};
501
- hdr.kind = NIPC_KIND_REQUEST;
502
- hdr.code = method_code;
503
- hdr.flags = 0;
504
- hdr.item_count = 1;
505
- hdr.message_id = (uint64_t)(ctx->call_count + 1);
506
- hdr.transport_status = NIPC_STATUS_OK;
507
-
508
- nipc_error_t err = transport_send(ctx, &hdr, request_payload, request_len);
509
- if (err != NIPC_OK)
510
- return err;
511
-
512
- nipc_header_t resp_hdr;
513
- err = transport_receive(ctx, ctx->response_buf, ctx->response_buf_size,
514
- &resp_hdr, response_payload_out, response_len_out);
515
- if (err != NIPC_OK)
516
- return err;
517
-
518
- if (resp_hdr.kind != NIPC_KIND_RESPONSE)
519
- return NIPC_ERR_BAD_KIND;
520
- if (resp_hdr.code != method_code)
521
- return NIPC_ERR_BAD_LAYOUT;
522
- if (resp_hdr.message_id != hdr.message_id)
523
- return NIPC_ERR_BAD_LAYOUT;
524
- switch (resp_hdr.transport_status) {
525
- case NIPC_STATUS_OK:
526
- break;
527
- case NIPC_STATUS_LIMIT_EXCEEDED:
528
- if (ctx->session.max_response_payload_bytes > 0) {
529
- uint32_t current = ctx->session.max_response_payload_bytes;
530
- client_note_response_capacity(
531
- ctx, current >= UINT32_MAX / 2u ? UINT32_MAX : current * 2u);
532
- }
533
- return NIPC_ERR_OVERFLOW;
534
- case NIPC_STATUS_UNSUPPORTED:
535
- return NIPC_ERR_BAD_LAYOUT;
536
- case NIPC_STATUS_BAD_ENVELOPE:
537
- case NIPC_STATUS_INTERNAL_ERROR:
538
- default:
539
- return NIPC_ERR_BAD_LAYOUT;
540
- }
541
-
542
- return NIPC_OK;
543
-}
544
-
545
-/*
546
- * Generic call-with-retry:
547
- * - ordinary failures reconnect and retry once
548
- * - overflow-driven resize recovery may reconnect repeatedly until
549
- * negotiated capacities grow or recovery fails
550
- * The caller provides a function pointer for the single-attempt logic.
551
- */
552
-typedef nipc_error_t (*nipc_attempt_fn)(nipc_client_ctx_t *ctx, void *state);
553
-
554
-static nipc_error_t call_with_retry(nipc_client_ctx_t *ctx,
555
- nipc_attempt_fn attempt,
556
- void *state)
557
-{
558
- if (ctx->state != NIPC_CLIENT_READY) {
559
- ctx->error_count++;
560
- return NIPC_ERR_NOT_READY;
561
- }
562
-
563
- /* Cap overflow-driven retries: payloads grow by powers of 2, so 8
564
- * retries allows ~256x growth from the initial negotiated size. */
565
- int overflow_retries = 0;
566
- for (;;) {
567
- uint32_t prev_req = ctx->session.max_request_payload_bytes;
568
- uint32_t prev_resp = ctx->session.max_response_payload_bytes;
569
- uint32_t prev_cfg_req = ctx->transport_config.max_request_payload_bytes;
570
- uint32_t prev_cfg_resp = ctx->transport_config.max_response_payload_bytes;
571
-
572
- nipc_error_t err = attempt(ctx, state);
573
- if (err == NIPC_OK) {
574
- ctx->call_count++;
575
- return NIPC_OK;
576
- }
577
-
578
- if (err != NIPC_ERR_OVERFLOW) {
579
- client_disconnect(ctx);
580
- ctx->state = NIPC_CLIENT_BROKEN;
581
- ctx->state = client_try_connect(ctx);
582
- if (ctx->state != NIPC_CLIENT_READY) {
583
- ctx->error_count++;
584
- return err;
585
- }
586
-
587
- ctx->reconnect_count++;
588
- err = attempt(ctx, state);
589
- if (err == NIPC_OK) {
590
- ctx->call_count++;
591
- return NIPC_OK;
592
- }
593
-
594
- client_disconnect(ctx);
595
- ctx->state = NIPC_CLIENT_BROKEN;
596
- ctx->error_count++;
597
- return err;
598
- }
599
-
600
- client_disconnect(ctx);
601
- ctx->state = NIPC_CLIENT_BROKEN;
602
- ctx->state = client_try_connect(ctx);
603
- if (ctx->state != NIPC_CLIENT_READY) {
604
- ctx->error_count++;
605
- return err;
606
- }
607
- ctx->reconnect_count++;
608
-
609
- if (ctx->session.max_request_payload_bytes <= prev_req &&
610
- ctx->session.max_response_payload_bytes <= prev_resp &&
611
- ctx->transport_config.max_request_payload_bytes <= prev_cfg_req &&
612
- ctx->transport_config.max_response_payload_bytes <= prev_cfg_resp) {
613
- client_disconnect(ctx);
614
- ctx->state = NIPC_CLIENT_BROKEN;
615
- ctx->error_count++;
616
- return err;
617
- }
618
-
619
- if (++overflow_retries >= 8) {
620
- client_disconnect(ctx);
621
- ctx->state = NIPC_CLIENT_BROKEN;
622
- ctx->error_count++;
623
- return err;
624
- }
625
- }
626
-}
627
-
628
-/* ------------------------------------------------------------------ */
629
-/* Internal: single attempt at a cgroups snapshot call */
630
-/* ------------------------------------------------------------------ */
631
-
632
-typedef struct {
633
- nipc_cgroups_resp_view_t *view_out;
634
-} cgroups_call_state_t;
635
-
636
-static nipc_error_t do_cgroups_attempt(nipc_client_ctx_t *ctx, void *state)
637
-{
638
- cgroups_call_state_t *s = (cgroups_call_state_t *)state;
639
-
640
- nipc_cgroups_req_t req = { .layout_version = 1, .flags = 0 };
641
- uint8_t req_buf[4];
642
- size_t req_len = nipc_cgroups_req_encode(&req, req_buf, sizeof(req_buf));
643
- if (req_len == 0)
644
- return NIPC_ERR_TRUNCATED;
645
-
646
- const void *payload;
647
- size_t payload_len;
648
- nipc_error_t err = do_raw_call(ctx, NIPC_METHOD_CGROUPS_SNAPSHOT,
649
- req_buf, req_len,
650
- &payload, &payload_len);
651
- if (err != NIPC_OK)
652
- return err;
653
-
654
- return nipc_cgroups_resp_decode(payload, payload_len, s->view_out);
655
-}
656
-
657
-/* ------------------------------------------------------------------ */
658
-/* Public API: client lifecycle */
659
-/* ------------------------------------------------------------------ */
660
-
661
-void nipc_client_init(nipc_client_ctx_t *ctx,
662
- const char *run_dir,
663
- const char *service_name,
664
- const nipc_client_config_t *config)
665
-{
666
- memset(ctx, 0, sizeof(*ctx));
667
- ctx->state = NIPC_CLIENT_DISCONNECTED;
668
- ctx->session.pipe = INVALID_HANDLE_VALUE;
669
- ctx->session_valid = false;
670
- ctx->shm = NULL;
671
-
672
- if (run_dir) {
673
- size_t len = strlen(run_dir);
674
- if (len >= sizeof(ctx->run_dir))
675
- len = sizeof(ctx->run_dir) - 1;
676
- memcpy(ctx->run_dir, run_dir, len);
677
- ctx->run_dir[len] = '\0';
678
- }
679
-
680
- if (service_name) {
681
- size_t len = strlen(service_name);
682
- if (len >= sizeof(ctx->service_name))
683
- len = sizeof(ctx->service_name) - 1;
684
- memcpy(ctx->service_name, service_name, len);
685
- ctx->service_name[len] = '\0';
686
- }
687
-
688
- ctx->transport_config = service_client_config_to_transport(config);
689
- if (ctx->transport_config.max_request_payload_bytes == 0)
690
- ctx->transport_config.max_request_payload_bytes = cgroups_request_payload_default();
691
- if (ctx->transport_config.max_response_payload_bytes == 0)
692
- ctx->transport_config.max_response_payload_bytes = cgroups_response_payload_default();
693
-}
694
-
695
-bool nipc_client_refresh(nipc_client_ctx_t *ctx)
696
-{
697
- nipc_client_state_t old_state = ctx->state;
698
-
699
- switch (ctx->state) {
700
- case NIPC_CLIENT_DISCONNECTED:
701
- case NIPC_CLIENT_NOT_FOUND:
702
- ctx->state = NIPC_CLIENT_CONNECTING;
703
- ctx->state = client_try_connect(ctx);
704
- if (ctx->state == NIPC_CLIENT_READY)
705
- ctx->connect_count++;
706
- break;
707
-
708
- case NIPC_CLIENT_BROKEN:
709
- client_disconnect(ctx);
710
- ctx->state = NIPC_CLIENT_CONNECTING;
711
- ctx->state = client_try_connect(ctx);
712
- if (ctx->state == NIPC_CLIENT_READY)
713
- ctx->reconnect_count++;
714
- break;
715
-
716
- case NIPC_CLIENT_READY:
717
- case NIPC_CLIENT_CONNECTING:
718
- case NIPC_CLIENT_AUTH_FAILED:
719
- case NIPC_CLIENT_INCOMPATIBLE:
720
- break;
721
- }
722
-
723
- return ctx->state != old_state;
724
-}
725
-
726
-void nipc_client_status(const nipc_client_ctx_t *ctx,
727
- nipc_client_status_t *out)
728
-{
729
- out->state = ctx->state;
730
- out->connect_count = ctx->connect_count;
731
- out->reconnect_count = ctx->reconnect_count;
732
- out->call_count = ctx->call_count;
733
- out->error_count = ctx->error_count;
734
-}
735
-
736
-void nipc_client_close(nipc_client_ctx_t *ctx)
737
-{
738
- client_disconnect(ctx);
739
- free(ctx->response_buf);
740
- free(ctx->send_buf);
741
- ctx->response_buf = NULL;
742
- ctx->send_buf = NULL;
743
- ctx->response_buf_size = 0;
744
- ctx->send_buf_size = 0;
745
- ctx->state = NIPC_CLIENT_DISCONNECTED;
746
-}
747
-
748
-/* ------------------------------------------------------------------ */
749
-/* Public API: typed cgroups snapshot call */
750
-/* ------------------------------------------------------------------ */
751
-
752
-nipc_error_t nipc_client_call_cgroups_snapshot(
753
- nipc_client_ctx_t *ctx,
754
- nipc_cgroups_resp_view_t *view_out)
755
-{
756
- cgroups_call_state_t state = {
757
- .view_out = view_out,
758
- };
759
- return call_with_retry(ctx, do_cgroups_attempt, &state);
760
-}
761
-
762
-/* ------------------------------------------------------------------ */
763
-/* Internal: managed server session handler */
764
-/* ------------------------------------------------------------------ */
765
-
766
-/*
767
- * Handle one client session: read requests, dispatch to handler,
768
- * send responses. Each session gets its own response buffer.
769
- * Runs until the client disconnects or server stops.
770
- */
771
-static void server_handle_session(nipc_managed_server_t *server,
772
- nipc_np_session_t *session,
773
- nipc_win_shm_ctx_t *shm,
774
- uint8_t *resp_buf,
775
- size_t resp_buf_size)
776
-{
777
- /* Dynamically allocate recv buffer based on negotiated max */
778
- size_t recv_size;
779
- if (!header_payload_len(session->max_request_payload_bytes, &recv_size))
780
- return;
781
- if (recv_size < NIPC_HEADER_LEN + 1024u)
782
- recv_size = NIPC_HEADER_LEN + 1024u;
783
- uint8_t *recv_buf = service_malloc(
784
- recv_size, NIPC_WIN_SERVICE_TEST_FAULT_SERVER_RECV_BUF_MALLOC_INTERNAL);
785
- if (!recv_buf)
786
- return;
787
-
788
- while (InterlockedCompareExchange(&server->running, 0, 0)) {
789
- nipc_header_t hdr;
790
- const void *payload;
791
- size_t payload_len;
792
-
793
- /* Receive request via the active transport */
794
- if (shm) {
795
- size_t msg_len;
796
- nipc_win_shm_error_t serr = nipc_win_shm_receive(shm, recv_buf, recv_size,
797
- &msg_len, SERVER_POLL_TIMEOUT_MS);
798
- if (serr == NIPC_WIN_SHM_ERR_TIMEOUT)
799
- continue;
800
- if (serr != NIPC_WIN_SHM_OK)
801
- break;
802
- if (msg_len < NIPC_HEADER_LEN)
803
- break;
804
-
805
- nipc_error_t perr = nipc_header_decode(recv_buf, msg_len, &hdr);
806
- if (perr != NIPC_OK)
807
- break;
808
-
809
- payload = recv_buf + NIPC_HEADER_LEN;
810
- payload_len = msg_len - NIPC_HEADER_LEN;
811
- } else {
812
- /* Named Pipe path: wait for readability first, then receive.
813
- * This mirrors the Go/Rust Windows server loops and avoids
814
- * relying on a blocking ReadFile wake-up for each ping-pong
815
- * request. */
816
- bool readable = false;
817
- nipc_np_error_t werr = nipc_np_wait_readable(
818
- session, SERVER_POLL_TIMEOUT_MS, &readable);
819
- if (werr == NIPC_NP_ERR_DISCONNECTED)
820
- break;
821
- if (werr != NIPC_NP_OK)
822
- break;
823
- if (!readable)
824
- continue;
825
-
826
- nipc_np_error_t uerr = nipc_np_receive(
827
- session, recv_buf, recv_size,
828
- &hdr, &payload, &payload_len);
829
- if (uerr == NIPC_NP_ERR_LIMIT_EXCEEDED) {
830
- if (hdr.kind == NIPC_KIND_REQUEST) {
831
- if (hdr.payload_len > 0)
832
- server_note_request_capacity(server, hdr.payload_len);
833
-
834
- nipc_header_t resp_hdr = {0};
835
- resp_hdr.kind = NIPC_KIND_RESPONSE;
836
- resp_hdr.code = hdr.code;
837
- resp_hdr.message_id = hdr.message_id;
838
- resp_hdr.transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
839
- resp_hdr.item_count = 1;
840
- resp_hdr.flags = 0;
841
-
842
- if (nipc_np_send(session, &resp_hdr, NULL, 0) != NIPC_NP_OK)
843
- break;
844
- }
845
- break;
846
- }
847
- if (uerr != NIPC_NP_OK)
848
- break;
849
- }
850
-
851
- /* Protocol violation: unexpected message kind terminates session */
852
- if (hdr.kind != NIPC_KIND_REQUEST)
853
- break;
854
-
855
- if (hdr.code != server->expected_method_code) {
856
- nipc_header_t resp_hdr = {0};
857
- resp_hdr.kind = NIPC_KIND_RESPONSE;
858
- resp_hdr.code = hdr.code;
859
- resp_hdr.message_id = hdr.message_id;
860
- resp_hdr.transport_status = NIPC_STATUS_UNSUPPORTED;
861
- resp_hdr.item_count = 1;
862
- resp_hdr.flags = 0;
863
-
864
- if (shm) {
865
- uint8_t msg[NIPC_HEADER_LEN];
866
- resp_hdr.magic = NIPC_MAGIC_MSG;
867
- resp_hdr.version = NIPC_VERSION;
868
- resp_hdr.header_len = NIPC_HEADER_LEN;
869
- resp_hdr.payload_len = 0;
870
- nipc_header_encode(&resp_hdr, msg, sizeof(msg));
871
- if (nipc_win_shm_send(shm, msg, sizeof(msg)) != NIPC_WIN_SHM_OK)
872
- break;
873
- } else {
874
- if (nipc_np_send(session, &resp_hdr, NULL, 0) != NIPC_NP_OK)
875
- break;
876
- }
877
- continue;
878
- }
879
-
880
- if (payload_len <= UINT32_MAX)
881
- server_note_request_capacity(server, (uint32_t)payload_len);
882
-
883
- /* Dispatch: one request kind per service endpoint. */
884
- size_t response_len = 0;
885
- nipc_error_t dispatch_err = server->handler(
886
- server->handler_user,
887
- &hdr,
888
- (const uint8_t *)payload, payload_len,
889
- resp_buf, resp_buf_size,
890
- &response_len);
891
-
892
- /* Build response header */
893
- nipc_header_t resp_hdr = {0};
894
- resp_hdr.kind = NIPC_KIND_RESPONSE;
895
- resp_hdr.code = hdr.code;
896
- resp_hdr.message_id = hdr.message_id;
897
- if ((hdr.flags & NIPC_FLAG_BATCH) && hdr.item_count >= 1) {
898
- resp_hdr.item_count = hdr.item_count;
899
- resp_hdr.flags = NIPC_FLAG_BATCH;
900
- } else {
901
- resp_hdr.item_count = 1;
902
- resp_hdr.flags = 0;
903
- }
904
-
905
- switch (dispatch_err) {
906
- case NIPC_OK:
907
- if (response_len > resp_buf_size ||
908
- response_len > session->max_response_payload_bytes ||
909
- response_len > SIZE_MAX - NIPC_HEADER_LEN) {
910
- server_note_response_capacity(
911
- server,
912
- response_len >= UINT32_MAX ? UINT32_MAX : (uint32_t)response_len);
913
- resp_hdr.transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
914
- response_len = 0;
915
- } else {
916
- if (response_len <= UINT32_MAX)
917
- server_note_response_capacity(server, (uint32_t)response_len);
918
- resp_hdr.transport_status = NIPC_STATUS_OK;
919
- }
920
- break;
921
- case NIPC_ERR_OVERFLOW:
922
- if (session->max_response_payload_bytes >= UINT32_MAX / 2u)
923
- server_note_response_capacity(server, UINT32_MAX);
924
- else
925
- server_note_response_capacity(server,
926
- session->max_response_payload_bytes * 2u);
927
- resp_hdr.transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
928
- response_len = 0;
929
- break;
930
- case NIPC_ERR_TRUNCATED:
931
- case NIPC_ERR_BAD_LAYOUT:
932
- case NIPC_ERR_OUT_OF_BOUNDS:
933
- case NIPC_ERR_MISSING_NUL:
934
- case NIPC_ERR_BAD_ALIGNMENT:
935
- case NIPC_ERR_BAD_ITEM_COUNT:
936
- resp_hdr.transport_status = NIPC_STATUS_BAD_ENVELOPE;
937
- response_len = 0;
938
- break;
939
- case NIPC_ERR_HANDLER_FAILED:
940
- default:
941
- resp_hdr.transport_status = NIPC_STATUS_INTERNAL_ERROR;
942
- response_len = 0;
943
- break;
944
- }
945
-
946
- /* Send response via the active transport */
947
- if (shm) {
948
- size_t msg_len;
949
- if (!header_payload_len(response_len, &msg_len))
950
- break;
951
-
952
- resp_hdr.magic = NIPC_MAGIC_MSG;
953
- resp_hdr.version = NIPC_VERSION;
954
- resp_hdr.header_len = NIPC_HEADER_LEN;
955
- resp_hdr.payload_len = (uint32_t)response_len;
956
-
957
- uint8_t stack_msg[4096];
958
- uint8_t *msg = (msg_len <= sizeof(stack_msg)) ? stack_msg : malloc(msg_len);
959
- if (!msg)
960
- break;
961
-
962
- nipc_header_encode(&resp_hdr, msg, NIPC_HEADER_LEN);
963
- if (response_len > 0)
964
- memcpy(msg + NIPC_HEADER_LEN, resp_buf, response_len);
965
-
966
- nipc_win_shm_error_t serr = nipc_win_shm_send(shm, msg, msg_len);
967
- if (msg != stack_msg)
968
- free(msg);
969
- if (serr != NIPC_WIN_SHM_OK)
970
- break;
971
- } else {
972
- nipc_np_error_t uerr = nipc_np_send(
973
- session, &resp_hdr, resp_buf, response_len);
974
- if (uerr != NIPC_NP_OK)
975
- break;
976
- }
977
-
978
- if (dispatch_err == NIPC_ERR_OVERFLOW)
979
- break;
980
- }
981
-
982
- free(recv_buf);
983
-}
984
-
985
-static uint32_t server_snapshot_max_items(size_t response_buf_size,
986
- const nipc_cgroups_service_handler_t *service_handler)
987
-{
988
- if (service_handler->snapshot_max_items != 0)
989
- return service_handler->snapshot_max_items;
990
- return nipc_cgroups_builder_estimate_max_items(response_buf_size);
991
-}
992
-
993
-static void server_note_request_capacity(nipc_managed_server_t *server,
994
- uint32_t payload_len)
995
-{
996
- uint32_t grown = next_power_of_2_u32(payload_len);
997
- uint32_t current = server->learned_request_payload_bytes;
998
- while (grown > current) {
999
- uint32_t previous = (uint32_t)InterlockedCompareExchange(
1000
- (volatile LONG *)&server->learned_request_payload_bytes,
1001
- (LONG)grown, (LONG)current);
1002
- if (previous == current)
1003
- break;
1004
- current = previous;
1005
- }
1006
-}
1007
-
1008
-static void server_note_response_capacity(nipc_managed_server_t *server,
1009
- uint32_t payload_len)
1010
-{
1011
- uint32_t grown = next_power_of_2_u32(payload_len);
1012
- uint32_t current = server->learned_response_payload_bytes;
1013
- while (grown > current) {
1014
- uint32_t previous = (uint32_t)InterlockedCompareExchange(
1015
- (volatile LONG *)&server->learned_response_payload_bytes,
1016
- (LONG)grown, (LONG)current);
1017
- if (previous == current)
1018
- break;
1019
- current = previous;
1020
- }
1021
-}
1022
-
1023
-static nipc_error_t server_typed_dispatch(void *user,
1024
- const nipc_header_t *request_hdr,
1025
- const uint8_t *request_payload,
1026
- size_t request_len,
1027
- uint8_t *response_buf,
1028
- size_t response_buf_size,
1029
- size_t *response_len_out)
1030
-{
1031
- nipc_managed_server_t *server = (nipc_managed_server_t *)user;
1032
- nipc_cgroups_service_handler_t *service_handler = &server->service_handler;
1033
- (void)request_hdr;
1034
-
1035
- if (!service_handler->handle)
1036
- return NIPC_ERR_HANDLER_FAILED;
1037
-
1038
- return nipc_dispatch_cgroups_snapshot(
1039
- request_payload, request_len,
1040
- response_buf, response_buf_size, response_len_out,
1041
- server_snapshot_max_items(response_buf_size, service_handler),
1042
- service_handler->handle, service_handler->user);
1043
-}
1044
-
1045
-/* ------------------------------------------------------------------ */
1046
-/* Internal: per-session handler thread */
1047
-/* ------------------------------------------------------------------ */
1048
-
1049
-/* Thread function: handles one client session from accept to disconnect. */
1050
-static unsigned __stdcall session_handler_thread(void *arg)
1051
-{
1052
- nipc_session_ctx_t *sctx = (nipc_session_ctx_t *)arg;
1053
- nipc_managed_server_t *server = sctx->server;
1054
- /* Allocate a per-session response buffer */
1055
- size_t resp_size = (size_t)sctx->session.max_response_payload_bytes;
1056
- if (resp_size < 1024u)
1057
- resp_size = 1024u;
1058
- uint8_t *resp_buf = service_malloc(
1059
- resp_size, NIPC_WIN_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL);
1060
- if (resp_buf) {
1061
- server_handle_session(server, &sctx->session, sctx->shm,
1062
- resp_buf, resp_size);
1063
- free(resp_buf);
1064
- }
1065
-
1066
- /* Cleanup SHM and session */
1067
- if (sctx->shm) {
1068
- nipc_win_shm_destroy(sctx->shm);
1069
- free(sctx->shm);
1070
- }
1071
- nipc_np_close_session(&sctx->session);
1072
-
1073
- /* Mark inactive; the reap/destroy path owns removal from the array */
1074
- InterlockedExchange((volatile LONG *)&sctx->active, 0);
1075
- return 0;
1076
-}
1077
-
1078
-/* ------------------------------------------------------------------ */
1079
-/* Internal: reap finished session threads */
1080
-/* ------------------------------------------------------------------ */
1081
-
1082
-/* Reap all finished (inactive) session threads. Called with lock held. */
1083
-static void server_reap_sessions_locked(nipc_managed_server_t *server)
1084
-{
1085
- int i = 0;
1086
- while (i < server->session_count) {
1087
- nipc_session_ctx_t *s = server->sessions[i];
1088
- if (!InterlockedCompareExchange((volatile LONG *)&s->active, 0, 0)) {
1089
- WaitForSingleObject(s->thread, INFINITE);
1090
- CloseHandle(s->thread);
1091
- /* Swap with last, free */
1092
- server->sessions[i] = server->sessions[server->session_count - 1];
1093
- server->session_count--;
1094
- free(s);
1095
- } else {
1096
- i++;
1097
- }
1098
- }
1099
-
1100
-}
1101
-
1102
-typedef struct {
1103
- nipc_win_shm_ctx_t *hybrid;
1104
- nipc_win_shm_ctx_t *busywait;
1105
-} prepared_win_shm_t;
1106
-
1107
-static void server_destroy_prepared_win_shm(prepared_win_shm_t *prepared)
1108
-{
1109
- if (!prepared)
1110
- return;
1111
- if (prepared->hybrid) {
1112
- nipc_win_shm_destroy(prepared->hybrid);
1113
- free(prepared->hybrid);
1114
- prepared->hybrid = NULL;
1115
- }
1116
- if (prepared->busywait) {
1117
- nipc_win_shm_destroy(prepared->busywait);
1118
- free(prepared->busywait);
1119
- prepared->busywait = NULL;
1120
- }
1121
-}
1122
-
1123
-static nipc_win_shm_ctx_t *server_take_prepared_win_shm(prepared_win_shm_t *prepared,
1124
- uint32_t profile)
1125
-{
1126
- if (!prepared)
1127
- return NULL;
1128
- if (profile == NIPC_WIN_SHM_PROFILE_HYBRID) {
1129
- nipc_win_shm_ctx_t *ctx = prepared->hybrid;
1130
- prepared->hybrid = NULL;
1131
- return ctx;
1132
- }
1133
- if (profile == NIPC_WIN_SHM_PROFILE_BUSYWAIT) {
1134
- nipc_win_shm_ctx_t *ctx = prepared->busywait;
1135
- prepared->busywait = NULL;
1136
- return ctx;
1137
- }
1138
- return NULL;
1139
-}
1140
-
1141
-static bool server_prepare_accept_config(nipc_managed_server_t *server,
1142
- uint64_t sid,
1143
- nipc_np_server_config_t *cfg_out,
1144
- prepared_win_shm_t *prepared)
1145
-{
1146
- *cfg_out = server->base_config;
1147
- cfg_out->max_request_payload_bytes = server->learned_request_payload_bytes;
1148
- cfg_out->max_response_payload_bytes = server->learned_response_payload_bytes;
1149
- memset(prepared, 0, sizeof(*prepared));
1150
-
1151
- uint32_t shm_profiles = cfg_out->supported_profiles &
1152
- (NIPC_WIN_SHM_PROFILE_HYBRID |
1153
- NIPC_WIN_SHM_PROFILE_BUSYWAIT);
1154
- if (shm_profiles == 0)
1155
- return true;
1156
-
1157
- uint32_t request_capacity;
1158
- uint32_t response_capacity;
1159
- if (!header_payload_len_u32(NIPC_MAX_PAYLOAD_CAP, &request_capacity) ||
1160
- !header_payload_len_u32(cfg_out->max_response_payload_bytes, &response_capacity)) {
1161
- cfg_out->supported_profiles &= ~shm_profiles;
1162
- cfg_out->preferred_profiles &= ~shm_profiles;
1163
- return cfg_out->supported_profiles != 0;
1164
- }
1165
-
1166
- const uint32_t profiles[] = {
1167
- NIPC_WIN_SHM_PROFILE_HYBRID,
1168
- NIPC_WIN_SHM_PROFILE_BUSYWAIT,
1169
- };
1170
- for (size_t i = 0; i < sizeof(profiles) / sizeof(profiles[0]); i++) {
1171
- uint32_t profile = profiles[i];
1172
- if (!(cfg_out->supported_profiles & profile))
1173
- continue;
1174
-
1175
- nipc_win_shm_ctx_t *ctx = service_calloc(
1176
- 1, sizeof(nipc_win_shm_ctx_t),
1177
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SHM_CTX_CALLOC_INTERNAL);
1178
- if (!ctx)
1179
- continue;
1180
-
1181
- /* HELLO has not been read yet, so the request segment must cover any
1182
- * client proposal the handshake may legally echo back. */
1183
- nipc_win_shm_error_t serr = nipc_win_shm_server_create(
1184
- server->run_dir, server->service_name,
1185
- server->auth_token,
1186
- sid,
1187
- profile,
1188
- request_capacity,
1189
- response_capacity,
1190
- ctx);
1191
- if (serr == NIPC_WIN_SHM_OK) {
1192
- if (profile == NIPC_WIN_SHM_PROFILE_HYBRID)
1193
- prepared->hybrid = ctx;
1194
- else
1195
- prepared->busywait = ctx;
1196
- continue;
1197
- }
1198
-
1199
- free(ctx);
1200
- cfg_out->supported_profiles &= ~profile;
1201
- cfg_out->preferred_profiles &= ~profile;
1202
- }
1203
-
1204
- return cfg_out->supported_profiles != 0;
1205
-}
1206
-
1207
-static void server_wake_listener(nipc_managed_server_t *server)
1208
-{
1209
- if (server->listener.pipe == INVALID_HANDLE_VALUE ||
1210
- server->listener.pipe == NULL ||
1211
- server->listener.pipe_name[0] == L'\0')
1212
- return;
1213
-
1214
- HANDLE wake = CreateFileW(
1215
- server->listener.pipe_name,
1216
- GENERIC_READ | GENERIC_WRITE,
1217
- 0,
1218
- NULL,
1219
- OPEN_EXISTING,
1220
- 0,
1221
- NULL);
1222
- if (wake != INVALID_HANDLE_VALUE)
1223
- CloseHandle(wake);
1224
-}
1225
-
1226
-/* Ask active session threads to leave synchronous ReadFile/WriteFile waits.
1227
- * This is safer than targeting pipe handles from another thread because the
1228
- * thread handle stays valid until the owner joins it. */
1229
-static void server_cancel_active_session_io_locked(nipc_managed_server_t *server)
1230
-{
1231
- for (int i = 0; i < server->session_count; i++) {
1232
- nipc_session_ctx_t *s = server->sessions[i];
1233
- if (!s)
1234
- continue;
1235
- if (!InterlockedCompareExchange((volatile LONG *)&s->active, 0, 0))
1236
- continue;
1237
- if (s->thread == NULL || s->thread == INVALID_HANDLE_VALUE)
1238
- continue;
1239
- CancelSynchronousIo(s->thread);
1240
- }
1241
-}
1242
-
1243
-/* ------------------------------------------------------------------ */
1244
-/* Public API: managed server */
1245
-/* ------------------------------------------------------------------ */
1246
-
1247
-static nipc_error_t server_init_raw(nipc_managed_server_t *server,
1248
- const char *run_dir,
1249
- const char *service_name,
1250
- const nipc_np_server_config_t *config,
1251
- int worker_count,
1252
- uint16_t expected_method_code,
1253
- nipc_server_handler_fn handler,
1254
- void *user)
1255
-{
1256
- if (!server)
1257
- return NIPC_ERR_BAD_LAYOUT;
1258
-
1259
- memset(server, 0, sizeof(*server));
1260
- server->listener.pipe = INVALID_HANDLE_VALUE;
1261
- InterlockedExchange(&server->running, 0);
1262
-
1263
- if (!run_dir || !service_name || !config || !handler)
1264
- return NIPC_ERR_BAD_LAYOUT;
1265
-
1266
- if (worker_count < 1)
1267
- worker_count = 1;
1268
-
1269
- /* Store config */
1270
- {
1271
- size_t len = strlen(run_dir);
1272
- if (len >= sizeof(server->run_dir))
1273
- len = sizeof(server->run_dir) - 1;
1274
- memcpy(server->run_dir, run_dir, len);
1275
- server->run_dir[len] = '\0';
1276
- }
1277
- {
1278
- size_t len = strlen(service_name);
1279
- if (len >= sizeof(server->service_name))
1280
- len = sizeof(server->service_name) - 1;
1281
- memcpy(server->service_name, service_name, len);
1282
- server->service_name[len] = '\0';
1283
- }
1284
-
1285
- server->handler = handler;
1286
- server->handler_user = user;
1287
- server->worker_count = worker_count;
1288
- server->expected_method_code = expected_method_code;
1289
- server->base_config = *config;
1290
- server->learned_request_payload_bytes =
1291
- (config && config->max_request_payload_bytes > 0)
1292
- ? config->max_request_payload_bytes
1293
- : NIPC_MAX_PAYLOAD_DEFAULT;
1294
- server->learned_response_payload_bytes =
1295
- (config && config->max_response_payload_bytes > 0)
1296
- ? config->max_response_payload_bytes
1297
- : NIPC_MAX_PAYLOAD_DEFAULT;
1298
- server->auth_token = config ? config->auth_token : 0;
1299
-
1300
-
1301
- /* Initialize session tracking */
1302
- server->session_capacity = worker_count * 2;
1303
- if (server->session_capacity < 16)
1304
- server->session_capacity = 16;
1305
- server->sessions = service_calloc(
1306
- (size_t)server->session_capacity, sizeof(nipc_session_ctx_t *),
1307
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SESSIONS_CALLOC_INTERNAL);
1308
- if (!server->sessions)
1309
- return NIPC_ERR_OVERFLOW;
1310
- server->session_count = 0;
1311
- server->next_session_id = 1; /* spec: monotonic counter starting at 1 */
1312
- InitializeCriticalSection(&server->sessions_lock);
1313
-
1314
- /* Clean up stale SHM kernel objects from previous crashes (no-op on
1315
- * Windows but maintains API symmetry with the POSIX transport). */
1316
- nipc_win_shm_cleanup_stale(run_dir, service_name);
1317
-
1318
- /* Start listening via L1 */
1319
- nipc_np_error_t uerr = nipc_np_listen(
1320
- run_dir, service_name, config, &server->listener);
1321
- if (uerr != NIPC_NP_OK) {
1322
- free(server->sessions);
1323
- server->sessions = NULL;
1324
- DeleteCriticalSection(&server->sessions_lock);
1325
- return NIPC_ERR_BAD_LAYOUT;
1326
- }
1327
-
1328
- return NIPC_OK;
1329
-}
1330
-
1331
-nipc_error_t nipc_server_init_typed(nipc_managed_server_t *server,
1332
- const char *run_dir,
1333
- const char *service_name,
1334
- const nipc_server_config_t *config,
1335
- int worker_count,
1336
- const nipc_cgroups_service_handler_t *service_handler)
1337
-{
1338
- if (!service_handler)
1339
- return NIPC_ERR_BAD_LAYOUT;
1340
-
1341
- nipc_np_server_config_t typed_cfg = service_server_config_to_transport(config);
1342
- if (typed_cfg.max_request_payload_bytes == 0)
1343
- typed_cfg.max_request_payload_bytes = cgroups_request_payload_default();
1344
- if (typed_cfg.max_response_payload_bytes == 0)
1345
- typed_cfg.max_response_payload_bytes = cgroups_response_payload_default();
1346
-
1347
- nipc_error_t err = server_init_raw(server, run_dir, service_name,
1348
- &typed_cfg, worker_count,
1349
- NIPC_METHOD_CGROUPS_SNAPSHOT,
1350
- server_typed_dispatch, server);
1351
- if (err != NIPC_OK)
1352
- return err;
1353
-
1354
- server->service_handler = *service_handler;
1355
- return NIPC_OK;
1356
-}
1357
-
1358
-nipc_error_t nipc_server_init_raw_for_tests(nipc_managed_server_t *server,
1359
- const char *run_dir,
1360
- const char *service_name,
1361
- const nipc_np_server_config_t *config,
1362
- int worker_count,
1363
- uint16_t expected_method_code,
1364
- nipc_server_handler_fn handler,
1365
- void *user)
1366
-{
1367
- return server_init_raw(server, run_dir, service_name, config,
1368
- worker_count, expected_method_code, handler, user);
1369
-}
1370
-
1371
-void nipc_server_run(nipc_managed_server_t *server)
1372
-{
1373
- InterlockedExchange(&server->accept_loop_active, 1);
1374
- InterlockedExchange(&server->running, 1);
1375
-
1376
- while (InterlockedCompareExchange(&server->running, 0, 0)) {
1377
- /* Accept one client via L1 (blocking with internal timeout) */
1378
- nipc_np_session_t session;
1379
- memset(&session, 0, sizeof(session));
1380
- session.pipe = INVALID_HANDLE_VALUE;
1381
-
1382
- uint64_t sid = server->next_session_id++;
1383
- nipc_np_server_config_t accept_cfg;
1384
- prepared_win_shm_t prepared_shm;
1385
- if (!server_prepare_accept_config(server, sid, &accept_cfg, &prepared_shm)) {
1386
- Sleep(10);
1387
- continue;
1388
- }
1389
-
1390
- server->listener.config = accept_cfg;
1391
- nipc_np_error_t uerr = nipc_np_accept(&server->listener, sid, &session);
1392
- if (uerr != NIPC_NP_OK) {
1393
- server_destroy_prepared_win_shm(&prepared_shm);
1394
- if (!InterlockedCompareExchange(&server->running, 0, 0))
1395
- break;
1396
- Sleep(10);
1397
- continue;
1398
- }
1399
-
1400
- server_note_request_capacity(server, session.max_request_payload_bytes);
1401
- server_note_response_capacity(server, session.max_response_payload_bytes);
1402
-
1403
- /* Enforce worker_count limit: reap finished sessions, check count */
1404
- EnterCriticalSection(&server->sessions_lock);
1405
- server_reap_sessions_locked(server);
1406
-
1407
- if (server->session_count >= server->worker_count) {
1408
- /* At capacity: reject this client by closing the session */
1409
- LeaveCriticalSection(&server->sessions_lock);
1410
- server_destroy_prepared_win_shm(&prepared_shm);
1411
- nipc_np_close_session(&session);
1412
- continue;
1413
- }
1414
-
1415
- /* SHM profile guarantee: only negotiate SHM for sessions backed by
1416
- * prepared per-session kernel objects for the selected profile. */
1417
- nipc_win_shm_ctx_t *shm = NULL;
1418
- if (session.selected_profile == NIPC_WIN_SHM_PROFILE_HYBRID ||
1419
- session.selected_profile == NIPC_WIN_SHM_PROFILE_BUSYWAIT) {
1420
- shm = server_take_prepared_win_shm(&prepared_shm, session.selected_profile);
1421
- if (!shm) {
1422
- server_destroy_prepared_win_shm(&prepared_shm);
1423
- LeaveCriticalSection(&server->sessions_lock);
1424
- nipc_np_close_session(&session);
1425
- continue;
1426
- }
1427
- server_destroy_prepared_win_shm(&prepared_shm);
1428
- } else {
1429
- server_destroy_prepared_win_shm(&prepared_shm);
1430
- }
1431
-
1432
- /* Create session context */
1433
- nipc_session_ctx_t *sctx = service_calloc(
1434
- 1, sizeof(nipc_session_ctx_t),
1435
- NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SESSION_CTX_CALLOC_INTERNAL);
1436
- if (!sctx) {
1437
- LeaveCriticalSection(&server->sessions_lock);
1438
- if (shm) { nipc_win_shm_destroy(shm); free(shm); }
1439
- nipc_np_close_session(&session);
1440
- continue;
1441
- }
1442
-
1443
- sctx->server = server;
1444
- sctx->session = session;
1445
- sctx->shm = shm;
1446
- sctx->id = sid;
1447
- InterlockedExchange((volatile LONG *)&sctx->active, 1);
1448
-
1449
- server->sessions[server->session_count++] = sctx;
1450
- LeaveCriticalSection(&server->sessions_lock);
1451
-
1452
- /* Spawn handler thread for this session */
1453
- unsigned tid_unused;
1454
- sctx->thread = (HANDLE)service_beginthreadex(
1455
- NULL, 0, session_handler_thread, sctx, 0, &tid_unused);
1456
- if (sctx->thread == 0) {
1457
- /* Thread creation failed: clean up */
1458
- EnterCriticalSection(&server->sessions_lock);
1459
- for (int i = 0; i < server->session_count; i++) {
1460
- if (server->sessions[i] == sctx) {
1461
- server->sessions[i] = server->sessions[server->session_count - 1];
1462
- server->session_count--;
1463
- break;
1464
- }
1465
- }
1466
- LeaveCriticalSection(&server->sessions_lock);
1467
-
1468
- if (shm) { nipc_win_shm_destroy(shm); free(shm); }
1469
- nipc_np_close_session(&session);
1470
- free(sctx);
1471
- }
1472
- }
1473
-
1474
- InterlockedExchange(&server->accept_loop_active, 0);
1475
- nipc_np_close_listener(&server->listener);
1476
-}
1477
-
1478
-void nipc_server_stop(nipc_managed_server_t *server)
1479
-{
1480
- InterlockedExchange(&server->running, 0);
1481
- if (InterlockedCompareExchange(&server->accept_loop_active, 0, 0))
1482
- server_wake_listener(server);
1483
- else
1484
- nipc_np_close_listener(&server->listener);
1485
-
1486
- if (server->sessions) {
1487
- EnterCriticalSection(&server->sessions_lock);
1488
- server_cancel_active_session_io_locked(server);
1489
- LeaveCriticalSection(&server->sessions_lock);
1490
- }
1491
-}
1492
-
1493
-bool nipc_server_drain(nipc_managed_server_t *server, uint32_t timeout_ms)
1494
-{
1495
- /* 1. Stop accepting new clients */
1496
- InterlockedExchange(&server->running, 0);
1497
- if (InterlockedCompareExchange(&server->accept_loop_active, 0, 0))
1498
- server_wake_listener(server);
1499
- else
1500
- nipc_np_close_listener(&server->listener);
1501
-
1502
- /* 2. Wait for in-flight sessions to complete */
1503
- bool all_drained = true;
1504
- if (server->sessions) {
1505
- ULONGLONG deadline = GetTickCount64() + timeout_ms;
1506
-
1507
- /* Poll until all sessions are inactive or timeout */
1508
- while (1) {
1509
- EnterCriticalSection(&server->sessions_lock);
1510
- int active_count = 0;
1511
- for (int i = 0; i < server->session_count; i++) {
1512
- if (InterlockedCompareExchange(
1513
- (volatile LONG *)&server->sessions[i]->active, 0, 0))
1514
- active_count++;
1515
- }
1516
- LeaveCriticalSection(&server->sessions_lock);
1517
-
1518
- if (active_count == 0)
1519
- break;
1520
-
1521
- if (GetTickCount64() >= deadline) {
1522
- /* Timeout: cancel synchronous session I/O to unblock threads. */
1523
- EnterCriticalSection(&server->sessions_lock);
1524
- server_cancel_active_session_io_locked(server);
1525
- LeaveCriticalSection(&server->sessions_lock);
1526
- all_drained = false;
1527
- break;
1528
- }
1529
-
1530
- Sleep(5); /* 5ms poll interval */
1531
- }
1532
-
1533
- /* 3. Join all session threads */
1534
- EnterCriticalSection(&server->sessions_lock);
1535
- for (int i = 0; i < server->session_count; i++) {
1536
- nipc_session_ctx_t *s = server->sessions[i];
1537
- LeaveCriticalSection(&server->sessions_lock);
1538
- WaitForSingleObject(s->thread, INFINITE);
1539
- CloseHandle(s->thread);
1540
- free(s);
1541
- EnterCriticalSection(&server->sessions_lock);
1542
- }
1543
- server->session_count = 0;
1544
- LeaveCriticalSection(&server->sessions_lock);
1545
-
1546
- free(server->sessions);
1547
- server->sessions = NULL;
1548
- server->session_capacity = 0;
1549
- DeleteCriticalSection(&server->sessions_lock);
1550
- }
1551
-
1552
- server->worker_count = 0;
1553
-
1554
- return all_drained;
1555
-}
1556
-
1557
-void nipc_server_destroy(nipc_managed_server_t *server)
1558
-{
1559
- InterlockedExchange(&server->running, 0);
1560
- if (InterlockedCompareExchange(&server->accept_loop_active, 0, 0))
1561
- server_wake_listener(server);
1562
- else
1563
- nipc_np_close_listener(&server->listener);
1564
-
1565
- /* Join all active session threads */
1566
- if (server->sessions) {
1567
- EnterCriticalSection(&server->sessions_lock);
1568
- server_cancel_active_session_io_locked(server);
1569
- for (int i = 0; i < server->session_count; i++) {
1570
- nipc_session_ctx_t *s = server->sessions[i];
1571
- LeaveCriticalSection(&server->sessions_lock);
1572
- WaitForSingleObject(s->thread, INFINITE);
1573
- CloseHandle(s->thread);
1574
- free(s);
1575
- EnterCriticalSection(&server->sessions_lock);
1576
- }
1577
- server->session_count = 0;
1578
- LeaveCriticalSection(&server->sessions_lock);
1579
-
1580
- free(server->sessions);
1581
- server->sessions = NULL;
1582
- server->session_capacity = 0;
1583
- DeleteCriticalSection(&server->sessions_lock);
1584
- }
1585
-
1586
- server->worker_count = 0;
1587
-
1588
-}
1589
-
1590
-/* ------------------------------------------------------------------ */
1591
-/* L3: Client-side cgroups snapshot cache */
1592
-/* ------------------------------------------------------------------ */
1593
-
1594
-/* Free all owned strings in cache items and the items array itself. */
1595
-static void cache_free_items(nipc_cgroups_cache_item_t *items, uint32_t count)
1596
-{
1597
- if (!items)
1598
- return;
1599
-
1600
- for (uint32_t i = 0; i < count; i++) {
1601
- free(items[i].name);
1602
- free(items[i].path);
1603
- }
1604
- free(items);
1605
-}
1606
-
1607
-/* Hash a name string (djb2). Combined with item hash for bucket index. */
1608
-static uint32_t cache_hash_name(const char *name)
1609
-{
1610
- uint32_t h = 5381;
1611
- for (const unsigned char *p = (const unsigned char *)name; *p; p++)
1612
- h = ((h << 5) + h) + *p;
1613
- return h;
1614
-}
1615
-
1616
-/*
1617
- * Build the open-addressing hash table from the items array.
1618
- * Uses (item.hash ^ name_hash) as the probe key.
1619
- * Load factor <= 0.5 (bucket_count >= 2 * item_count).
1620
- */
1621
-static bool cache_build_hashtable(nipc_cgroups_cache_t *cache)
1622
-{
1623
- free(cache->buckets);
1624
- cache->buckets = NULL;
1625
- cache->bucket_count = 0;
1626
-
1627
- if (cache->item_count == 0)
1628
- return true;
1629
-
1630
- uint32_t bcount = next_power_of_2_u32(cache->item_count * 2);
1631
- nipc_cgroups_hash_bucket_t *buckets = service_calloc(
1632
- bcount, sizeof(nipc_cgroups_hash_bucket_t),
1633
- NIPC_WIN_SERVICE_TEST_FAULT_CACHE_BUCKETS_CALLOC_INTERNAL);
1634
- if (!buckets)
1635
- return false;
1636
-
1637
- uint32_t mask = bcount - 1;
1638
- for (uint32_t i = 0; i < cache->item_count; i++) {
1639
- uint32_t key = cache->items[i].hash ^ cache_hash_name(cache->items[i].name);
1640
- uint32_t slot = key & mask;
1641
-
1642
- /* Linear probe for an empty bucket */
1643
- while (buckets[slot].used)
1644
- slot = (slot + 1) & mask;
1645
-
1646
- buckets[slot].index = i;
1647
- buckets[slot].used = true;
1648
- }
1649
-
1650
- cache->buckets = buckets;
1651
- cache->bucket_count = bcount;
1652
- return true;
1653
-}
1654
-
1655
-static nipc_cgroups_cache_item_t *cache_build_items(
1656
- const nipc_cgroups_resp_view_t *view,
1657
- uint32_t *count_out)
151
+static uint64_t monotonic_time_ms(void)
152
{
1659
- uint32_t n = view->item_count;
1660
- *count_out = 0;
1661
-
1662
- if (n == 0)
1663
- return NULL;
1664
-
1665
- nipc_cgroups_cache_item_t *items = service_calloc(
1666
- n, sizeof(nipc_cgroups_cache_item_t),
1667
- NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEMS_CALLOC_INTERNAL);
1668
- if (!items)
1669
- return NULL;
1670
-
1671
- for (uint32_t i = 0; i < n; i++) {
1672
- nipc_cgroups_item_view_t iv;
1673
- nipc_error_t err = nipc_cgroups_resp_item(view, i, &iv);
1674
- if (err != NIPC_OK) {
1675
- cache_free_items(items, i);
1676
- return NULL;
1677
- }
1678
-
1679
- items[i].hash = iv.hash;
1680
- items[i].options = iv.options;
1681
- items[i].enabled = iv.enabled;
1682
-
1683
- items[i].name = service_malloc(
1684
- iv.name.len + 1, NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEM_NAME_MALLOC_INTERNAL);
1685
- if (!items[i].name) {
1686
- cache_free_items(items, i);
1687
- return NULL;
1688
- }
1689
- if (iv.name.len > 0)
1690
- memcpy(items[i].name, iv.name.ptr, iv.name.len);
1691
- items[i].name[iv.name.len] = '\0';
1692
-
1693
- items[i].path = service_malloc(
1694
- iv.path.len + 1, NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEM_PATH_MALLOC_INTERNAL);
1695
- if (!items[i].path) {
1696
- free(items[i].name);
1697
- cache_free_items(items, i);
1698
- return NULL;
1699
- }
1700
- if (iv.path.len > 0)
1701
- memcpy(items[i].path, iv.path.ptr, iv.path.len);
1702
- items[i].path[iv.path.len] = '\0';
1703
- }
1704
-
1705
- *count_out = n;
1706
- return items;
1707
-}
1708
-
1709
-void nipc_cgroups_cache_init(nipc_cgroups_cache_t *cache,
1710
- const char *run_dir,
1711
- const char *service_name,
1712
- const nipc_client_config_t *config)
1713
-{
1714
- memset(cache, 0, sizeof(*cache));
1715
-
1716
- nipc_client_init(&cache->client, run_dir, service_name, config);
1717
-
1718
- cache->items = NULL;
1719
- cache->item_count = 0;
1720
- cache->systemd_enabled = 0;
1721
- cache->generation = 0;
1722
- cache->populated = false;
1723
- cache->buckets = NULL;
1724
- cache->bucket_count = 0;
1725
- cache->refresh_success_count = 0;
1726
- cache->refresh_failure_count = 0;
1727
-
1728
- cache->response_buf = NULL;
1729
- cache->response_buf_size = 0;
1730
-}
1731
-
1732
-bool nipc_cgroups_cache_refresh(nipc_cgroups_cache_t *cache)
1733
-{
1734
- nipc_client_refresh(&cache->client);
1735
-
1736
- nipc_cgroups_resp_view_t view;
1737
- nipc_error_t err = nipc_client_call_cgroups_snapshot(&cache->client, &view);
1738
-
1739
- if (err != NIPC_OK) {
1740
- cache->refresh_failure_count++;
1741
- return false;
1742
- }
1743
-
1744
- uint32_t new_count = 0;
1745
- nipc_cgroups_cache_item_t *new_items = NULL;
1746
-
1747
- if (view.item_count > 0) {
1748
- new_items = cache_build_items(&view, &new_count);
1749
- if (!new_items && view.item_count > 0) {
1750
- cache->refresh_failure_count++;
1751
- return false;
1752
- }
1753
- }
1754
-
1755
- cache_free_items(cache->items, cache->item_count);
1756
- cache->items = new_items;
1757
- cache->item_count = new_count;
1758
- cache->systemd_enabled = view.systemd_enabled;
1759
- cache->generation = view.generation;
1760
- cache->populated = true;
1761
- cache->refresh_success_count++;
1762
-
1763
- /* Record monotonic timestamp (GetTickCount64 is always available on Windows) */
1764
- cache->last_refresh_ts = GetTickCount64();
1765
-
1766
- /* Rebuild hash table for O(1) lookup */
1767
- cache_build_hashtable(cache);
1768
-
1769
- return true;
153
+ return GetTickCount64();
154
}
155
1772
-const nipc_cgroups_cache_item_t *nipc_cgroups_cache_lookup(
1773
- const nipc_cgroups_cache_t *cache,
1774
- uint32_t hash,
1775
- const char *name)
156
+uint64_t nipc_service_platform_monotonic_ms(void)
157
{
1777
- if (!cache->populated || !cache->items || !name)
1778
- return NULL;
1779
-
1780
- /* Use hash table if available, fall back to linear scan */
1781
- if (cache->buckets && cache->bucket_count > 0) {
1782
- uint32_t key = hash ^ cache_hash_name(name);
1783
- uint32_t mask = cache->bucket_count - 1;
1784
- uint32_t slot = key & mask;
1785
-
1786
- while (cache->buckets[slot].used) {
1787
- uint32_t idx = cache->buckets[slot].index;
1788
- if (cache->items[idx].hash == hash &&
1789
- strcmp(cache->items[idx].name, name) == 0) {
1790
- return &cache->items[idx];
1791
- }
1792
- slot = (slot + 1) & mask;
1793
- }
1794
- return NULL;
1795
- }
1796
-
1797
- /* Fallback linear scan (hash table allocation failed) */
1798
- for (uint32_t i = 0; i < cache->item_count; i++) {
1799
- if (cache->items[i].hash == hash &&
1800
- strcmp(cache->items[i].name, name) == 0) {
1801
- return &cache->items[i];
1802
- }
1803
- }
1804
-
1805
- return NULL;
158
+ return monotonic_time_ms();
159
}
160
1808
-void nipc_cgroups_cache_status(const nipc_cgroups_cache_t *cache,
1809
- nipc_cgroups_cache_status_t *out)
161
+bool nipc_service_platform_ensure_client_send_buffer(nipc_client_ctx_t *ctx,
162
+ size_t need)
163
{
1811
- out->populated = cache->populated;
1812
- out->item_count = cache->item_count;
1813
- out->systemd_enabled = cache->systemd_enabled;
1814
- out->generation = cache->generation;
1815
- out->refresh_success_count = cache->refresh_success_count;
1816
- out->refresh_failure_count = cache->refresh_failure_count;
1817
- out->connection_state = cache->client.state;
1818
- out->last_refresh_ts = cache->last_refresh_ts;
164
+ return nipc_service_win_ensure_buffer(&ctx->send_buf, &ctx->send_buf_size, need,
165
+ NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL);
166
}
167
1821
-void nipc_cgroups_cache_close(nipc_cgroups_cache_t *cache)
1822
-{
1823
- cache_free_items(cache->items, cache->item_count);
1824
- cache->items = NULL;
1825
- cache->item_count = 0;
1826
- cache->populated = false;
1827
-
1828
- free(cache->buckets);
1829
- cache->buckets = NULL;
1830
- cache->bucket_count = 0;
1831
-
1832
- free(cache->response_buf);
1833
- cache->response_buf = NULL;
1834
- cache->response_buf_size = 0;
1835
-
1836
- nipc_client_close(&cache->client);
1837
-}
168
+/* Managed server implementation lives in netipc_service_win_server.c. */
169
170
#endif /* _WIN32 || __MSYS__ */
src/libnetdata/netipc/src/service/netipc_service_win_client.c
new
+102
@@ -0,0 +1,102 @@
1
+/*
2
+ * netipc_service_win_client.c - WIN public service client API.
3
+ */
4
+
5
+#if defined(_WIN32) || defined(__MSYS__)
6
+
7
+#include "netipc/netipc_service.h"
8
+#include "netipc/netipc_protocol.h"
9
+#include "netipc/netipc_named_pipe.h"
10
+#include "netipc/netipc_win_shm.h"
11
+#include "netipc_service_common.h"
12
+#include "netipc_service_platform.h"
13
+#include "netipc_service_win_internal.h"
14
+
15
+#include <stdint.h>
16
+#include <stdlib.h>
17
+#include <string.h>
18
+#include <windows.h>
19
+
20
+static nipc_np_client_config_t service_client_config_to_transport(
21
+ const nipc_client_config_t *config)
22
+{
23
+ nipc_np_client_config_t transport = {0};
24
+ nipc_service_common_transport_fields_t fields;
25
+
26
+ if (!nipc_service_common_client_transport_fields(&fields, config))
27
+ return transport;
28
+
29
+ NIPC_SERVICE_COMMON_APPLY_TRANSPORT_FIELDS(&transport, &fields);
30
+ return transport;
31
+}
32
+
33
+static void client_sleep_ms(uint32_t ms)
34
+{
35
+ Sleep(ms);
36
+}
37
+
38
+const nipc_service_common_client_ops_t *nipc_service_win_client_ops(void)
39
+{
40
+ static const nipc_service_common_client_ops_t ops = {
41
+ .disconnect = nipc_service_win_client_disconnect,
42
+ .try_connect = nipc_service_win_client_try_connect,
43
+ .reconnect_for_call = nipc_service_win_client_reconnect_for_call,
44
+ .sleep_ms = client_sleep_ms,
45
+ .reconnect_drain_ms = 0,
46
+ .reconnect_retry_interval_ms = CLIENT_CALL_RECONNECT_RETRY_INTERVAL_MS,
47
+ };
48
+ return &ops;
49
+}
50
+
51
+void nipc_service_platform_server_config_from_service(
52
+ nipc_service_platform_server_config_t *transport,
53
+ const nipc_server_config_t *config)
54
+{
55
+ memset(transport, 0, sizeof(*transport));
56
+ nipc_service_common_transport_fields_t fields;
57
+
58
+ if (!nipc_service_common_server_transport_fields(&fields, config))
59
+ return;
60
+
61
+ NIPC_SERVICE_COMMON_APPLY_TRANSPORT_FIELDS(transport, &fields);
62
+}
63
+
64
+/* ------------------------------------------------------------------ */
65
+/* Public API: client lifecycle */
66
+/* ------------------------------------------------------------------ */
67
+
68
+void nipc_client_init(nipc_client_ctx_t *ctx,
69
+ const char *run_dir,
70
+ const char *service_name,
71
+ const nipc_client_config_t *config)
72
+{
73
+ nipc_service_common_client_init(ctx, run_dir, service_name);
74
+ ctx->session.pipe = INVALID_HANDLE_VALUE;
75
+
76
+ ctx->transport_config = service_client_config_to_transport(config);
77
+ if (ctx->transport_config.max_request_payload_bytes == 0)
78
+ ctx->transport_config.max_request_payload_bytes =
79
+ nipc_service_common_request_payload_default();
80
+ if (ctx->transport_config.max_response_payload_bytes == 0)
81
+ ctx->transport_config.max_response_payload_bytes =
82
+ nipc_service_common_response_payload_default();
83
+}
84
+
85
+bool nipc_client_refresh(nipc_client_ctx_t *ctx)
86
+{
87
+ return nipc_service_common_client_refresh(ctx, nipc_service_win_client_ops());
88
+}
89
+
90
+void nipc_client_status(const nipc_client_ctx_t *ctx,
91
+ nipc_client_status_t *out)
92
+{
93
+ nipc_service_common_client_status(ctx, out);
94
+}
95
+
96
+void nipc_client_close(nipc_client_ctx_t *ctx)
97
+{
98
+ nipc_service_win_client_disconnect(ctx);
99
+ nipc_service_common_client_close_buffers(ctx);
100
+}
101
+
102
+#endif /* _WIN32 || __MSYS__ */
src/libnetdata/netipc/src/service/netipc_service_win_client_call.c
new
+125
@@ -0,0 +1,125 @@
1
+/*
2
+ * netipc_service_win_client_call.c - WIN raw client call flow.
3
+ */
4
+
5
+#if defined(_WIN32) || defined(__MSYS__)
6
+
7
+#include "netipc/netipc_service.h"
8
+#include "netipc/netipc_protocol.h"
9
+#include "netipc/netipc_named_pipe.h"
10
+#include "netipc/netipc_win_shm.h"
11
+#include "netipc_service_common.h"
12
+#include "netipc_service_platform.h"
13
+#include "netipc_service_win_internal.h"
14
+
15
+#include <stdint.h>
16
+#include <stdlib.h>
17
+#include <string.h>
18
+#include <windows.h>
19
+
20
+/* ------------------------------------------------------------------ */
21
+/* Internal: send/receive via the active transport */
22
+/* ------------------------------------------------------------------ */
23
+
24
+static nipc_error_t transport_send(nipc_client_ctx_t *ctx,
25
+ nipc_header_t *hdr,
26
+ const void *payload,
27
+ size_t payload_len)
28
+{
29
+ if (payload_len > UINT32_MAX)
30
+ return NIPC_ERR_OVERFLOW;
31
+
32
+ if (ctx->shm) {
33
+ uint8_t *msg;
34
+ size_t msg_len;
35
+ nipc_error_t perr = nipc_service_common_client_prepare_shm_request(
36
+ ctx, hdr, payload, payload_len, &msg, &msg_len);
37
+ if (perr != NIPC_OK)
38
+ return perr;
39
+
40
+ nipc_win_shm_error_t serr = nipc_win_shm_send(ctx->shm, msg, msg_len);
41
+ if (serr == NIPC_WIN_SHM_ERR_MSG_TOO_LARGE) {
42
+ nipc_service_common_client_note_request_capacity(
43
+ ctx, (uint32_t)payload_len);
44
+ return NIPC_ERR_OVERFLOW;
45
+ }
46
+ return (serr == NIPC_WIN_SHM_OK) ? NIPC_OK : NIPC_ERR_NOT_READY;
47
+ }
48
+
49
+ /* Named Pipe path */
50
+ nipc_np_error_t uerr = nipc_np_send(&ctx->session, hdr,
51
+ payload, payload_len);
52
+ if (uerr == NIPC_NP_ERR_LIMIT_EXCEEDED) {
53
+ nipc_service_common_client_note_request_capacity(
54
+ ctx, (uint32_t)payload_len);
55
+ return NIPC_ERR_OVERFLOW;
56
+ }
57
+ return (uerr == NIPC_NP_OK) ? NIPC_OK : NIPC_ERR_NOT_READY;
58
+}
59
+
60
+static nipc_error_t transport_receive(nipc_client_ctx_t *ctx,
61
+ void *buf, size_t buf_size,
62
+ nipc_header_t *hdr_out,
63
+ const void **payload_out,
64
+ size_t *payload_len_out)
65
+{
66
+ if (ctx->shm) {
67
+ size_t msg_len;
68
+ nipc_win_shm_error_t serr = nipc_win_shm_receive(ctx->shm, buf, buf_size,
69
+ &msg_len, 30000);
70
+ if (serr != NIPC_WIN_SHM_OK)
71
+ return NIPC_ERR_TRUNCATED;
72
+
73
+ return nipc_service_common_client_parse_shm_response(
74
+ buf, msg_len, hdr_out, payload_out, payload_len_out);
75
+ }
76
+
77
+ /* Named Pipe path */
78
+ nipc_np_error_t uerr = nipc_np_receive(&ctx->session, buf, buf_size,
79
+ hdr_out, payload_out,
80
+ payload_len_out);
81
+ return (uerr == NIPC_NP_OK) ? NIPC_OK : NIPC_ERR_TRUNCATED;
82
+}
83
+
84
+/* ------------------------------------------------------------------ */
85
+/* Internal: generic raw call (send request, receive response) */
86
+/* ------------------------------------------------------------------ */
87
+
88
+/*
89
+ * Single-attempt raw call: build envelope, send, receive, validate
90
+ * envelope. The caller handles encode before and decode after.
91
+ *
92
+ * On success, response_payload_out and response_len_out point into the
93
+ * internal client response buffer (valid until next call on this context).
94
+ */
95
+nipc_error_t nipc_service_platform_do_raw_call(nipc_client_ctx_t *ctx,
96
+ uint16_t method_code,
97
+ const void *request_payload,
98
+ size_t request_len,
99
+ const void **response_payload_out,
100
+ size_t *response_len_out)
101
+{
102
+ return nipc_service_common_do_raw_call(
103
+ ctx, method_code, request_payload, request_len,
104
+ response_payload_out, response_len_out,
105
+ transport_send, transport_receive);
106
+}
107
+
108
+/*
109
+ * Generic call-with-retry:
110
+ * - ordinary failures reconnect and retry once
111
+ * - overflow-driven resize recovery may reconnect repeatedly until
112
+ * negotiated capacities grow or recovery fails
113
+ * The caller provides a function pointer for the single-attempt logic.
114
+ */
115
+nipc_error_t nipc_service_platform_call_with_retry(
116
+ nipc_client_ctx_t *ctx,
117
+ nipc_service_platform_attempt_fn attempt,
118
+ void *state)
119
+{
120
+ return nipc_service_common_call_with_retry(
121
+ ctx, attempt, state, nipc_service_win_client_ops());
122
+}
123
+
124
+
125
+#endif /* _WIN32 || __MSYS__ */
src/libnetdata/netipc/src/service/netipc_service_win_client_connect.c
new
+181
@@ -0,0 +1,181 @@
1
+/*
2
+ * netipc_service_win_client_connect.c - WIN client connection management.
3
+ */
4
+
5
+#if defined(_WIN32) || defined(__MSYS__)
6
+
7
+#include "netipc/netipc_service.h"
8
+#include "netipc/netipc_protocol.h"
9
+#include "netipc/netipc_named_pipe.h"
10
+#include "netipc/netipc_win_shm.h"
11
+#include "netipc_service_common.h"
12
+#include "netipc_service_platform.h"
13
+#include "netipc_service_win_internal.h"
14
+
15
+#include <stdint.h>
16
+#include <stdlib.h>
17
+#include <string.h>
18
+#include <windows.h>
19
+
20
+static bool client_prepare_session_buffers(nipc_client_ctx_t *ctx)
21
+{
22
+ size_t response_need;
23
+ if (!nipc_service_common_header_payload_len(
24
+ ctx->session.max_response_payload_bytes, &response_need))
25
+ return false;
26
+ if (response_need < NIPC_HEADER_LEN + 1024u)
27
+ response_need = NIPC_HEADER_LEN + 1024u;
28
+
29
+ if (!nipc_service_win_ensure_buffer(&ctx->response_buf, &ctx->response_buf_size, response_need,
30
+ NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_RESPONSE_BUF_REALLOC_INTERNAL))
31
+ return false;
32
+
33
+ if (ctx->session.selected_profile == NIPC_WIN_SHM_PROFILE_HYBRID ||
34
+ ctx->session.selected_profile == NIPC_WIN_SHM_PROFILE_BUSYWAIT) {
35
+ size_t send_need;
36
+ if (!nipc_service_common_header_payload_len(
37
+ ctx->session.max_request_payload_bytes, &send_need))
38
+ return false;
39
+ if (!nipc_service_win_ensure_buffer(&ctx->send_buf, &ctx->send_buf_size, send_need,
40
+ NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL))
41
+ return false;
42
+ }
43
+
44
+ return true;
45
+}
46
+
47
+/* ------------------------------------------------------------------ */
48
+/* Internal: client connection helpers */
49
+/* ------------------------------------------------------------------ */
50
+
51
+/* Tear down the current connection (Named Pipe session + Win SHM). */
52
+void nipc_service_win_client_disconnect(nipc_client_ctx_t *ctx)
53
+{
54
+ if (ctx->shm) {
55
+ nipc_win_shm_close(ctx->shm);
56
+ free(ctx->shm);
57
+ ctx->shm = NULL;
58
+ }
59
+
60
+ if (ctx->session_valid) {
61
+ nipc_np_close_session(&ctx->session);
62
+ ctx->session_valid = false;
63
+ }
64
+}
65
+
66
+static void client_disable_shm_profiles(nipc_client_ctx_t *ctx)
67
+{
68
+ ctx->transport_config.supported_profiles &=
69
+ ~(NIPC_WIN_SHM_PROFILE_HYBRID | NIPC_WIN_SHM_PROFILE_BUSYWAIT);
70
+ ctx->transport_config.preferred_profiles &=
71
+ ~(NIPC_WIN_SHM_PROFILE_HYBRID | NIPC_WIN_SHM_PROFILE_BUSYWAIT);
72
+}
73
+
74
+/* Attempt a full connection: Named Pipe connect + handshake, then
75
+ * Win SHM upgrade if negotiated. Returns the new state. */
76
+nipc_client_state_t nipc_service_win_client_try_connect(nipc_client_ctx_t *ctx)
77
+{
78
+ nipc_np_session_t session;
79
+ memset(&session, 0, sizeof(session));
80
+ session.pipe = INVALID_HANDLE_VALUE;
81
+
82
+ nipc_np_error_t err = nipc_np_connect(
83
+ ctx->run_dir, ctx->service_name,
84
+ &ctx->transport_config, &session);
85
+
86
+ switch (err) {
87
+ case NIPC_NP_OK:
88
+ break;
89
+ case NIPC_NP_ERR_CONNECT:
90
+ return NIPC_CLIENT_NOT_FOUND;
91
+ case NIPC_NP_ERR_AUTH_FAILED:
92
+ return NIPC_CLIENT_AUTH_FAILED;
93
+ case NIPC_NP_ERR_NO_PROFILE:
94
+ case NIPC_NP_ERR_INCOMPATIBLE:
95
+ return NIPC_CLIENT_INCOMPATIBLE;
96
+ default:
97
+ return NIPC_CLIENT_DISCONNECTED;
98
+ }
99
+
100
+ ctx->session = session;
101
+ ctx->session_valid = true;
102
+
103
+ if (!client_prepare_session_buffers(ctx)) {
104
+ nipc_np_close_session(&ctx->session);
105
+ ctx->session_valid = false;
106
+ return NIPC_CLIENT_DISCONNECTED;
107
+ }
108
+
109
+ /* Win SHM upgrade if negotiated */
110
+ if (session.selected_profile == NIPC_WIN_SHM_PROFILE_HYBRID ||
111
+ session.selected_profile == NIPC_WIN_SHM_PROFILE_BUSYWAIT) {
112
+
113
+ nipc_win_shm_ctx_t *shm = nipc_service_win_calloc(
114
+ 1, sizeof(nipc_win_shm_ctx_t),
115
+ NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SHM_CTX_CALLOC_INTERNAL);
116
+ if (!shm) {
117
+ nipc_np_close_session(&ctx->session);
118
+ ctx->session_valid = false;
119
+ return NIPC_CLIENT_DISCONNECTED;
120
+ }
121
+ {
122
+ /* Retry attach: the server prepares SHM before accepting the
123
+ * handshake, but filesystem/object visibility may lag briefly. */
124
+ nipc_win_shm_error_t serr = NIPC_WIN_SHM_ERR_OPEN_MAPPING;
125
+ ULONGLONG deadline_ms = GetTickCount64() + CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS;
126
+ for (;;) {
127
+ serr = nipc_win_shm_client_attach(
128
+ ctx->run_dir, ctx->service_name,
129
+ ctx->transport_config.auth_token,
130
+ session.session_id,
131
+ session.selected_profile,
132
+ shm);
133
+ if (serr == NIPC_WIN_SHM_OK)
134
+ break;
135
+ if (serr != NIPC_WIN_SHM_ERR_OPEN_MAPPING &&
136
+ serr != NIPC_WIN_SHM_ERR_OPEN_EVENT &&
137
+ serr != NIPC_WIN_SHM_ERR_BAD_MAGIC)
138
+ break;
139
+ if (GetTickCount64() >= deadline_ms)
140
+ break;
141
+ Sleep(CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS);
142
+ }
143
+
144
+ if (serr == NIPC_WIN_SHM_OK) {
145
+ ctx->shm = shm;
146
+ } else {
147
+ /* WinSHM attach failed after negotiation. Close that
148
+ * session, blacklist WinSHM for this client context, and
149
+ * retry baseline via a new handshake. */
150
+ free(shm);
151
+ nipc_np_close_session(&ctx->session);
152
+ ctx->session_valid = false;
153
+ client_disable_shm_profiles(ctx);
154
+ if (ctx->transport_config.supported_profiles == 0)
155
+ return NIPC_CLIENT_DISCONNECTED;
156
+ return nipc_service_win_client_try_connect(ctx);
157
+ }
158
+ }
159
+ }
160
+
161
+ return NIPC_CLIENT_READY;
162
+}
163
+
164
+bool nipc_service_win_client_reconnect_for_call(nipc_client_ctx_t *ctx)
165
+{
166
+ for (uint32_t i = 0; i < CLIENT_CALL_RECONNECT_RETRIES; i++) {
167
+ ctx->state = nipc_service_win_client_try_connect(ctx);
168
+ if (ctx->state == NIPC_CLIENT_READY)
169
+ return true;
170
+ if (ctx->state == NIPC_CLIENT_AUTH_FAILED ||
171
+ ctx->state == NIPC_CLIENT_INCOMPATIBLE)
172
+ return false;
173
+ if (i + 1u < CLIENT_CALL_RECONNECT_RETRIES)
174
+ Sleep(CLIENT_CALL_RECONNECT_RETRY_INTERVAL_MS);
175
+ }
176
+
177
+ return false;
178
+}
179
+
180
+
181
+#endif /* _WIN32 || __MSYS__ */
src/libnetdata/netipc/src/service/netipc_service_win_internal.h
new
+55
@@ -0,0 +1,55 @@
1
+#ifndef NETIPC_SERVICE_WIN_INTERNAL_H
2
+#define NETIPC_SERVICE_WIN_INTERNAL_H
3
+
4
+#if defined(_WIN32) || defined(__MSYS__)
5
+
6
+#include "netipc_service_platform.h"
7
+
8
+#include <process.h>
9
+#include <stdbool.h>
10
+#include <stddef.h>
11
+#include <stdint.h>
12
+#include <windows.h>
13
+
14
+#define SERVER_POLL_TIMEOUT_MS 100
15
+#define CLIENT_SHM_ATTACH_RETRY_INTERVAL_MS 5u
16
+#define CLIENT_SHM_ATTACH_RETRY_TIMEOUT_MS 5000u
17
+#define CLIENT_CALL_RECONNECT_RETRY_INTERVAL_MS 5u
18
+#define CLIENT_CALL_RECONNECT_RETRIES 20u
19
+
20
+enum {
21
+ NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_RESPONSE_BUF_REALLOC_INTERNAL = 1,
22
+ NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SEND_BUF_REALLOC_INTERNAL,
23
+ NIPC_WIN_SERVICE_TEST_FAULT_CLIENT_SHM_CTX_CALLOC_INTERNAL,
24
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SHM_CTX_CALLOC_INTERNAL,
25
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_RECV_BUF_MALLOC_INTERNAL,
26
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL,
27
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SESSIONS_CALLOC_INTERNAL,
28
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SESSION_CTX_CALLOC_INTERNAL,
29
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_THREAD_CREATE_INTERNAL,
30
+ NIPC_WIN_SERVICE_TEST_FAULT_CACHE_BUCKETS_CALLOC_INTERNAL,
31
+ NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEMS_CALLOC_INTERNAL,
32
+ NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEM_NAME_MALLOC_INTERNAL,
33
+ NIPC_WIN_SERVICE_TEST_FAULT_CACHE_ITEM_PATH_MALLOC_INTERNAL,
34
+};
35
+
36
+const nipc_service_common_client_ops_t *nipc_service_win_client_ops(void);
37
+void nipc_service_win_client_disconnect(nipc_client_ctx_t *ctx);
38
+nipc_client_state_t nipc_service_win_client_try_connect(nipc_client_ctx_t *ctx);
39
+bool nipc_service_win_client_reconnect_for_call(nipc_client_ctx_t *ctx);
40
+void *nipc_service_win_malloc(size_t size, int fault_site);
41
+void *nipc_service_win_calloc(size_t count, size_t size, int fault_site);
42
+bool nipc_service_win_ensure_buffer(uint8_t **buf, size_t *buf_size,
43
+ size_t need, int fault_site);
44
+unsigned __stdcall nipc_service_win_session_handler_thread(void *arg);
45
+void nipc_service_win_server_reap_sessions_locked(nipc_managed_server_t *server);
46
+uintptr_t nipc_service_win_beginthreadex(void *security,
47
+ unsigned stack_size,
48
+ unsigned (__stdcall *start_address)(void *),
49
+ void *arglist,
50
+ unsigned initflag,
51
+ unsigned *thrdaddr);
52
+
53
+#endif /* _WIN32 || __MSYS__ */
54
+
55
+#endif /* NETIPC_SERVICE_WIN_INTERNAL_H */
src/libnetdata/netipc/src/service/netipc_service_win_server.c
new
+464
@@ -0,0 +1,464 @@
1
+/*
2
+ * netipc_service_win_server.c - Windows managed server orchestration.
3
+ */
4
+
5
+#if defined(_WIN32) || defined(__MSYS__)
6
+
7
+#include "netipc/netipc_service.h"
8
+#include "netipc/netipc_protocol.h"
9
+#include "netipc/netipc_named_pipe.h"
10
+#include "netipc/netipc_win_shm.h"
11
+#include "netipc_service_common.h"
12
+#include "netipc_service_platform.h"
13
+#include "netipc_service_win_internal.h"
14
+
15
+#include <stdint.h>
16
+#include <stdlib.h>
17
+#include <string.h>
18
+#include <windows.h>
19
+
20
+/* ------------------------------------------------------------------ */
21
+/* Internal: managed server session handler */
22
+/* ------------------------------------------------------------------ */
23
+
24
+/*
25
+ * Handle one client session: read requests, dispatch to handler,
26
+ * send responses. Each session gets its own response buffer.
27
+ * Runs until the client disconnects or server stops.
28
+ */
29
+typedef struct {
30
+ nipc_win_shm_ctx_t *hybrid;
31
+ nipc_win_shm_ctx_t *busywait;
32
+} prepared_win_shm_t;
33
+
34
+static void server_destroy_prepared_win_shm(prepared_win_shm_t *prepared)
35
+{
36
+ if (!prepared)
37
+ return;
38
+ if (prepared->hybrid) {
39
+ nipc_win_shm_destroy(prepared->hybrid);
40
+ free(prepared->hybrid);
41
+ prepared->hybrid = NULL;
42
+ }
43
+ if (prepared->busywait) {
44
+ nipc_win_shm_destroy(prepared->busywait);
45
+ free(prepared->busywait);
46
+ prepared->busywait = NULL;
47
+ }
48
+}
49
+
50
+static nipc_win_shm_ctx_t *server_take_prepared_win_shm(prepared_win_shm_t *prepared,
51
+ uint32_t profile)
52
+{
53
+ if (!prepared)
54
+ return NULL;
55
+ if (profile == NIPC_WIN_SHM_PROFILE_HYBRID) {
56
+ nipc_win_shm_ctx_t *ctx = prepared->hybrid;
57
+ prepared->hybrid = NULL;
58
+ return ctx;
59
+ }
60
+ if (profile == NIPC_WIN_SHM_PROFILE_BUSYWAIT) {
61
+ nipc_win_shm_ctx_t *ctx = prepared->busywait;
62
+ prepared->busywait = NULL;
63
+ return ctx;
64
+ }
65
+ return NULL;
66
+}
67
+
68
+static bool server_prepare_accept_config(nipc_managed_server_t *server,
69
+ uint64_t sid,
70
+ nipc_np_server_config_t *cfg_out,
71
+ prepared_win_shm_t *prepared)
72
+{
73
+ *cfg_out = server->base_config;
74
+ cfg_out->max_request_payload_bytes = server->learned_request_payload_bytes;
75
+ cfg_out->max_response_payload_bytes = server->learned_response_payload_bytes;
76
+ memset(prepared, 0, sizeof(*prepared));
77
+
78
+ uint32_t shm_profiles = cfg_out->supported_profiles &
79
+ (NIPC_WIN_SHM_PROFILE_HYBRID |
80
+ NIPC_WIN_SHM_PROFILE_BUSYWAIT);
81
+ if (shm_profiles == 0)
82
+ return true;
83
+
84
+ uint32_t request_capacity;
85
+ uint32_t response_capacity;
86
+ if (!nipc_service_common_header_payload_len_u32(
87
+ NIPC_MAX_PAYLOAD_CAP, &request_capacity) ||
88
+ !nipc_service_common_header_payload_len_u32(
89
+ cfg_out->max_response_payload_bytes, &response_capacity)) {
90
+ cfg_out->supported_profiles &= ~shm_profiles;
91
+ cfg_out->preferred_profiles &= ~shm_profiles;
92
+ return cfg_out->supported_profiles != 0;
93
+ }
94
+
95
+ const uint32_t profiles[] = {
96
+ NIPC_WIN_SHM_PROFILE_HYBRID,
97
+ NIPC_WIN_SHM_PROFILE_BUSYWAIT,
98
+ };
99
+ for (size_t i = 0; i < sizeof(profiles) / sizeof(profiles[0]); i++) {
100
+ uint32_t profile = profiles[i];
101
+ if (!(cfg_out->supported_profiles & profile))
102
+ continue;
103
+
104
+ nipc_win_shm_ctx_t *ctx = nipc_service_win_calloc(
105
+ 1, sizeof(nipc_win_shm_ctx_t),
106
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SHM_CTX_CALLOC_INTERNAL);
107
+ if (!ctx)
108
+ continue;
109
+
110
+ /* HELLO has not been read yet, so the request segment must cover any
111
+ * client proposal the handshake may legally echo back. */
112
+ nipc_win_shm_error_t serr = nipc_win_shm_server_create(
113
+ server->run_dir, server->service_name,
114
+ server->auth_token,
115
+ sid,
116
+ profile,
117
+ request_capacity,
118
+ response_capacity,
119
+ ctx);
120
+ if (serr == NIPC_WIN_SHM_OK) {
121
+ if (profile == NIPC_WIN_SHM_PROFILE_HYBRID)
122
+ prepared->hybrid = ctx;
123
+ else
124
+ prepared->busywait = ctx;
125
+ continue;
126
+ }
127
+
128
+ free(ctx);
129
+ cfg_out->supported_profiles &= ~profile;
130
+ cfg_out->preferred_profiles &= ~profile;
131
+ }
132
+
133
+ return cfg_out->supported_profiles != 0;
134
+}
135
+
136
+static void server_wake_listener(nipc_managed_server_t *server)
137
+{
138
+ if (server->listener.pipe == INVALID_HANDLE_VALUE ||
139
+ server->listener.pipe == NULL ||
140
+ server->listener.pipe_name[0] == L'\0')
141
+ return;
142
+
143
+ HANDLE wake = CreateFileW(
144
+ server->listener.pipe_name,
145
+ GENERIC_READ | GENERIC_WRITE,
146
+ 0,
147
+ NULL,
148
+ OPEN_EXISTING,
149
+ 0,
150
+ NULL);
151
+ if (wake != INVALID_HANDLE_VALUE)
152
+ CloseHandle(wake);
153
+}
154
+
155
+/* Ask active session threads to leave synchronous ReadFile/WriteFile waits.
156
+ * This is safer than targeting pipe handles from another thread because the
157
+ * thread handle stays valid until the owner joins it. */
158
+static void server_cancel_active_session_io_locked(nipc_managed_server_t *server)
159
+{
160
+ for (int i = 0; i < server->session_count; i++) {
161
+ nipc_session_ctx_t *s = server->sessions[i];
162
+ if (!s)
163
+ continue;
164
+ if (!InterlockedCompareExchange((volatile LONG *)&s->active, 0, 0))
165
+ continue;
166
+ if (s->thread == NULL || s->thread == INVALID_HANDLE_VALUE)
167
+ continue;
168
+ CancelSynchronousIo(s->thread);
169
+ }
170
+}
171
+
172
+/* ------------------------------------------------------------------ */
173
+/* Public API: managed server */
174
+/* ------------------------------------------------------------------ */
175
+
176
+nipc_error_t nipc_service_platform_server_init_raw(
177
+ nipc_managed_server_t *server,
178
+ const char *run_dir,
179
+ const char *service_name,
180
+ const nipc_service_platform_server_config_t *config,
181
+ int worker_count,
182
+ uint16_t expected_method_code,
183
+ nipc_server_handler_fn handler,
184
+ void *user)
185
+{
186
+ if (!server)
187
+ return NIPC_ERR_BAD_LAYOUT;
188
+
189
+ memset(server, 0, sizeof(*server));
190
+ server->listener.pipe = INVALID_HANDLE_VALUE;
191
+ InterlockedExchange(&server->running, 0);
192
+
193
+ if (!config)
194
+ return NIPC_ERR_BAD_LAYOUT;
195
+
196
+ nipc_error_t ierr = nipc_service_common_server_init_base(
197
+ server, run_dir, service_name, worker_count, expected_method_code,
198
+ handler, user, config->max_request_payload_bytes,
199
+ config->max_response_payload_bytes);
200
+ if (ierr != NIPC_OK)
201
+ return ierr;
202
+ server->base_config = *config;
203
+ server->auth_token = config->auth_token;
204
+ ierr = nipc_service_common_server_alloc_sessions(
205
+ server, nipc_service_win_calloc,
206
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SESSIONS_CALLOC_INTERNAL);
207
+ if (ierr != NIPC_OK)
208
+ return ierr;
209
+ InitializeCriticalSection(&server->sessions_lock);
210
+
211
+ /* Clean up stale SHM kernel objects from previous crashes (no-op on
212
+ * Windows but maintains API symmetry with the POSIX transport). */
213
+ nipc_win_shm_cleanup_stale(run_dir, service_name);
214
+
215
+ /* Start listening via L1 */
216
+ nipc_np_error_t uerr = nipc_np_listen(
217
+ run_dir, service_name, config, &server->listener);
218
+ if (uerr != NIPC_NP_OK) {
219
+ free(server->sessions);
220
+ server->sessions = NULL;
221
+ DeleteCriticalSection(&server->sessions_lock);
222
+ return NIPC_ERR_BAD_LAYOUT;
223
+ }
224
+
225
+ return NIPC_OK;
226
+}
227
+
228
+nipc_error_t nipc_server_init_raw_for_tests(nipc_managed_server_t *server,
229
+ const char *run_dir,
230
+ const char *service_name,
231
+ const nipc_np_server_config_t *config,
232
+ int worker_count,
233
+ uint16_t expected_method_code,
234
+ nipc_server_handler_fn handler,
235
+ void *user)
236
+{
237
+ return nipc_service_platform_server_init_raw(
238
+ server, run_dir, service_name, config,
239
+ worker_count, expected_method_code, handler, user);
240
+}
241
+
242
+void nipc_server_run(nipc_managed_server_t *server)
243
+{
244
+ InterlockedExchange(&server->accept_loop_active, 1);
245
+ InterlockedExchange(&server->running, 1);
246
+
247
+ while (InterlockedCompareExchange(&server->running, 0, 0)) {
248
+ /* Accept one client via L1 (blocking with internal timeout) */
249
+ nipc_np_session_t session;
250
+ memset(&session, 0, sizeof(session));
251
+ session.pipe = INVALID_HANDLE_VALUE;
252
+
253
+ uint64_t sid = server->next_session_id++;
254
+ nipc_np_server_config_t accept_cfg;
255
+ prepared_win_shm_t prepared_shm;
256
+ if (!server_prepare_accept_config(server, sid, &accept_cfg, &prepared_shm)) {
257
+ Sleep(10);
258
+ continue;
259
+ }
260
+
261
+ server->listener.config = accept_cfg;
262
+ nipc_np_error_t uerr = nipc_np_accept(&server->listener, sid, &session);
263
+ if (uerr != NIPC_NP_OK) {
264
+ server_destroy_prepared_win_shm(&prepared_shm);
265
+ if (!InterlockedCompareExchange(&server->running, 0, 0))
266
+ break;
267
+ Sleep(10);
268
+ continue;
269
+ }
270
+
271
+ nipc_service_common_server_note_request_capacity(
272
+ server, session.max_request_payload_bytes);
273
+ nipc_service_common_server_note_response_capacity(
274
+ server, session.max_response_payload_bytes);
275
+
276
+ /* Enforce worker_count limit: reap finished sessions, check count */
277
+ EnterCriticalSection(&server->sessions_lock);
278
+ nipc_service_win_server_reap_sessions_locked(server);
279
+
280
+ if (server->session_count >= server->worker_count) {
281
+ /* At capacity: reject this client by closing the session */
282
+ LeaveCriticalSection(&server->sessions_lock);
283
+ server_destroy_prepared_win_shm(&prepared_shm);
284
+ nipc_np_close_session(&session);
285
+ continue;
286
+ }
287
+
288
+ /* SHM profile guarantee: only negotiate SHM for sessions backed by
289
+ * prepared per-session kernel objects for the selected profile. */
290
+ nipc_win_shm_ctx_t *shm = NULL;
291
+ if (session.selected_profile == NIPC_WIN_SHM_PROFILE_HYBRID ||
292
+ session.selected_profile == NIPC_WIN_SHM_PROFILE_BUSYWAIT) {
293
+ shm = server_take_prepared_win_shm(&prepared_shm, session.selected_profile);
294
+ if (!shm) {
295
+ server_destroy_prepared_win_shm(&prepared_shm);
296
+ LeaveCriticalSection(&server->sessions_lock);
297
+ nipc_np_close_session(&session);
298
+ continue;
299
+ }
300
+ server_destroy_prepared_win_shm(&prepared_shm);
301
+ } else {
302
+ server_destroy_prepared_win_shm(&prepared_shm);
303
+ }
304
+
305
+ /* Create session context */
306
+ nipc_session_ctx_t *sctx = nipc_service_win_calloc(
307
+ 1, sizeof(nipc_session_ctx_t),
308
+ NIPC_WIN_SERVICE_TEST_FAULT_SERVER_SESSION_CTX_CALLOC_INTERNAL);
309
+ if (!sctx) {
310
+ LeaveCriticalSection(&server->sessions_lock);
311
+ if (shm) { nipc_win_shm_destroy(shm); free(shm); }
312
+ nipc_np_close_session(&session);
313
+ continue;
314
+ }
315
+
316
+ sctx->server = server;
317
+ sctx->session = session;
318
+ sctx->shm = shm;
319
+ sctx->id = sid;
320
+ InterlockedExchange((volatile LONG *)&sctx->active, 1);
321
+
322
+ server->sessions[server->session_count++] = sctx;
323
+ LeaveCriticalSection(&server->sessions_lock);
324
+
325
+ /* Spawn handler thread for this session */
326
+ unsigned tid_unused;
327
+ sctx->thread = (HANDLE)nipc_service_win_beginthreadex(
328
+ NULL, 0, nipc_service_win_session_handler_thread, sctx, 0, &tid_unused);
329
+ if (sctx->thread == 0) {
330
+ /* Thread creation failed: clean up */
331
+ EnterCriticalSection(&server->sessions_lock);
332
+ for (int i = 0; i < server->session_count; i++) {
333
+ if (server->sessions[i] == sctx) {
334
+ server->sessions[i] = server->sessions[server->session_count - 1];
335
+ server->session_count--;
336
+ break;
337
+ }
338
+ }
339
+ LeaveCriticalSection(&server->sessions_lock);
340
+
341
+ if (shm) { nipc_win_shm_destroy(shm); free(shm); }
342
+ nipc_np_close_session(&session);
343
+ free(sctx);
344
+ }
345
+ }
346
+
347
+ InterlockedExchange(&server->accept_loop_active, 0);
348
+ nipc_np_close_listener(&server->listener);
349
+}
350
+
351
+void nipc_server_stop(nipc_managed_server_t *server)
352
+{
353
+ InterlockedExchange(&server->running, 0);
354
+ if (InterlockedCompareExchange(&server->accept_loop_active, 0, 0))
355
+ server_wake_listener(server);
356
+ else
357
+ nipc_np_close_listener(&server->listener);
358
+
359
+ if (server->sessions) {
360
+ EnterCriticalSection(&server->sessions_lock);
361
+ server_cancel_active_session_io_locked(server);
362
+ LeaveCriticalSection(&server->sessions_lock);
363
+ }
364
+}
365
+
366
+bool nipc_server_drain(nipc_managed_server_t *server, uint32_t timeout_ms)
367
+{
368
+ /* 1. Stop accepting new clients */
369
+ InterlockedExchange(&server->running, 0);
370
+ if (InterlockedCompareExchange(&server->accept_loop_active, 0, 0))
371
+ server_wake_listener(server);
372
+ else
373
+ nipc_np_close_listener(&server->listener);
374
+
375
+ /* 2. Wait for in-flight sessions to complete */
376
+ bool all_drained = true;
377
+ if (server->sessions) {
378
+ ULONGLONG deadline = GetTickCount64() + timeout_ms;
379
+
380
+ /* Poll until all sessions are inactive or timeout */
381
+ while (1) {
382
+ EnterCriticalSection(&server->sessions_lock);
383
+ int active_count = 0;
384
+ for (int i = 0; i < server->session_count; i++) {
385
+ if (InterlockedCompareExchange(
386
+ (volatile LONG *)&server->sessions[i]->active, 0, 0))
387
+ active_count++;
388
+ }
389
+ LeaveCriticalSection(&server->sessions_lock);
390
+
391
+ if (active_count == 0)
392
+ break;
393
+
394
+ if (GetTickCount64() >= deadline) {
395
+ /* Timeout: cancel synchronous session I/O to unblock threads. */
396
+ EnterCriticalSection(&server->sessions_lock);
397
+ server_cancel_active_session_io_locked(server);
398
+ LeaveCriticalSection(&server->sessions_lock);
399
+ all_drained = false;
400
+ break;
401
+ }
402
+
403
+ Sleep(5); /* 5ms poll interval */
404
+ }
405
+
406
+ /* 3. Join all session threads */
407
+ EnterCriticalSection(&server->sessions_lock);
408
+ for (int i = 0; i < server->session_count; i++) {
409
+ nipc_session_ctx_t *s = server->sessions[i];
410
+ LeaveCriticalSection(&server->sessions_lock);
411
+ WaitForSingleObject(s->thread, INFINITE);
412
+ CloseHandle(s->thread);
413
+ free(s);
414
+ EnterCriticalSection(&server->sessions_lock);
415
+ }
416
+ server->session_count = 0;
417
+ LeaveCriticalSection(&server->sessions_lock);
418
+
419
+ free(server->sessions);
420
+ server->sessions = NULL;
421
+ server->session_capacity = 0;
422
+ DeleteCriticalSection(&server->sessions_lock);
423
+ }
424
+
425
+ server->worker_count = 0;
426
+
427
+ return all_drained;
428
+}
429
+
430
+void nipc_server_destroy(nipc_managed_server_t *server)
431
+{
432
+ InterlockedExchange(&server->running, 0);
433
+ if (InterlockedCompareExchange(&server->accept_loop_active, 0, 0))
434
+ server_wake_listener(server);
435
+ else
436
+ nipc_np_close_listener(&server->listener);
437
+
438
+ /* Join all active session threads */
439
+ if (server->sessions) {
440
+ EnterCriticalSection(&server->sessions_lock);
441
+ server_cancel_active_session_io_locked(server);
442
+ for (int i = 0; i < server->session_count; i++) {
443
+ nipc_session_ctx_t *s = server->sessions[i];
444
+ LeaveCriticalSection(&server->sessions_lock);
445
+ WaitForSingleObject(s->thread, INFINITE);
446
+ CloseHandle(s->thread);
447
+ free(s);
448
+ EnterCriticalSection(&server->sessions_lock);
449
+ }
450
+ server->session_count = 0;
451
+ LeaveCriticalSection(&server->sessions_lock);
452
+
453
+ free(server->sessions);
454
+ server->sessions = NULL;
455
+ server->session_capacity = 0;
456
+ DeleteCriticalSection(&server->sessions_lock);
457
+ }
458
+
459
+ server->worker_count = 0;
460
+
461
+}
462
+
463
+
464
+#endif /* _WIN32 || __MSYS__ */
src/libnetdata/netipc/src/service/netipc_service_win_server_session.c
new
+249
@@ -0,0 +1,249 @@
1
+/*
2
+ * netipc_service_win_server_session.c - Windows managed server session loop.
3
+ */
4
+
5
+#if defined(_WIN32) || defined(__MSYS__)
6
+
7
+#include "netipc/netipc_service.h"
8
+#include "netipc/netipc_protocol.h"
9
+#include "netipc/netipc_named_pipe.h"
10
+#include "netipc/netipc_win_shm.h"
11
+#include "netipc_service_common.h"
12
+#include "netipc_service_win_internal.h"
13
+
14
+#include <stdint.h>
15
+#include <stdlib.h>
16
+#include <string.h>
17
+#include <windows.h>
18
+
19
+static void server_handle_session(nipc_managed_server_t *server,
20
+ nipc_np_session_t *session,
21
+ nipc_win_shm_ctx_t *shm,
22
+ uint8_t *resp_buf,
23
+ size_t resp_buf_size)
24
+{
25
+ /* Dynamically allocate recv buffer based on negotiated max */
26
+ size_t recv_size;
27
+ if (!nipc_service_common_header_payload_len(
28
+ session->max_request_payload_bytes, &recv_size))
29
+ return;
30
+ if (recv_size < NIPC_HEADER_LEN + 1024u)
31
+ recv_size = NIPC_HEADER_LEN + 1024u;
32
+ uint8_t *recv_buf = nipc_service_win_malloc(
33
+ recv_size, NIPC_WIN_SERVICE_TEST_FAULT_SERVER_RECV_BUF_MALLOC_INTERNAL);
34
+ if (!recv_buf)
35
+ return;
36
+
37
+ while (InterlockedCompareExchange(&server->running, 0, 0)) {
38
+ nipc_header_t hdr;
39
+ const void *payload;
40
+ size_t payload_len;
41
+
42
+ /* Receive request via the active transport */
43
+ if (shm) {
44
+ size_t msg_len;
45
+ nipc_win_shm_error_t serr = nipc_win_shm_receive(shm, recv_buf, recv_size,
46
+ &msg_len, SERVER_POLL_TIMEOUT_MS);
47
+ if (serr == NIPC_WIN_SHM_ERR_TIMEOUT)
48
+ continue;
49
+ if (serr != NIPC_WIN_SHM_OK)
50
+ break;
51
+ if (msg_len < NIPC_HEADER_LEN)
52
+ break;
53
+
54
+ nipc_error_t perr = nipc_header_decode(recv_buf, msg_len, &hdr);
55
+ if (perr != NIPC_OK)
56
+ break;
57
+
58
+ payload = recv_buf + NIPC_HEADER_LEN;
59
+ payload_len = msg_len - NIPC_HEADER_LEN;
60
+ } else {
61
+ /* Named Pipe path: wait for readability first, then receive.
62
+ * This mirrors the Go/Rust Windows server loops and avoids
63
+ * relying on a blocking ReadFile wake-up for each ping-pong
64
+ * request. */
65
+ bool readable = false;
66
+ nipc_np_error_t werr = nipc_np_wait_readable(
67
+ session, SERVER_POLL_TIMEOUT_MS, &readable);
68
+ if (werr == NIPC_NP_ERR_DISCONNECTED)
69
+ break;
70
+ if (werr != NIPC_NP_OK)
71
+ break;
72
+ if (!readable)
73
+ continue;
74
+
75
+ nipc_np_error_t uerr = nipc_np_receive(
76
+ session, recv_buf, recv_size,
77
+ &hdr, &payload, &payload_len);
78
+ if (uerr == NIPC_NP_ERR_LIMIT_EXCEEDED) {
79
+ if (hdr.kind == NIPC_KIND_REQUEST) {
80
+ if (hdr.payload_len > 0)
81
+ nipc_service_common_server_note_request_capacity(
82
+ server, hdr.payload_len);
83
+
84
+ nipc_header_t resp_hdr = {0};
85
+ resp_hdr.kind = NIPC_KIND_RESPONSE;
86
+ resp_hdr.code = hdr.code;
87
+ resp_hdr.message_id = hdr.message_id;
88
+ resp_hdr.transport_status = NIPC_STATUS_LIMIT_EXCEEDED;
89
+ resp_hdr.item_count = 1;
90
+ resp_hdr.flags = 0;
91
+
92
+ if (nipc_np_send(session, &resp_hdr, NULL, 0) != NIPC_NP_OK)
93
+ break;
94
+ }
95
+ break;
96
+ }
97
+ if (uerr != NIPC_NP_OK)
98
+ break;
99
+ }
100
+
101
+ /* Protocol violation: unexpected message kind terminates session */
102
+ if (hdr.kind != NIPC_KIND_REQUEST)
103
+ break;
104
+
105
+ if (hdr.code != server->expected_method_code) {
106
+ nipc_header_t resp_hdr = {0};
107
+ resp_hdr.kind = NIPC_KIND_RESPONSE;
108
+ resp_hdr.code = hdr.code;
109
+ resp_hdr.message_id = hdr.message_id;
110
+ resp_hdr.transport_status = NIPC_STATUS_UNSUPPORTED;
111
+ resp_hdr.item_count = 1;
112
+ resp_hdr.flags = 0;
113
+
114
+ if (shm) {
115
+ uint8_t msg[NIPC_HEADER_LEN];
116
+ resp_hdr.magic = NIPC_MAGIC_MSG;
117
+ resp_hdr.version = NIPC_VERSION;
118
+ resp_hdr.header_len = NIPC_HEADER_LEN;
119
+ resp_hdr.payload_len = 0;
120
+ nipc_header_encode(&resp_hdr, msg, sizeof(msg));
121
+ if (nipc_win_shm_send(shm, msg, sizeof(msg)) != NIPC_WIN_SHM_OK)
122
+ break;
123
+ } else {
124
+ if (nipc_np_send(session, &resp_hdr, NULL, 0) != NIPC_NP_OK)
125
+ break;
126
+ }
127
+ continue;
128
+ }
129
+
130
+ if (payload_len <= UINT32_MAX)
131
+ nipc_service_common_server_note_request_capacity(
132
+ server, (uint32_t)payload_len);
133
+
134
+ /* Dispatch: one request kind per service endpoint. */
135
+ size_t response_len = 0;
136
+ nipc_error_t dispatch_err = server->handler(
137
+ server->handler_user,
138
+ &hdr,
139
+ (const uint8_t *)payload, payload_len,
140
+ resp_buf, resp_buf_size,
141
+ &response_len);
142
+
143
+ /* Build response header */
144
+ nipc_header_t resp_hdr;
145
+ bool close_after_response = false;
146
+ nipc_service_common_prepare_response_header(&hdr, &resp_hdr);
147
+ nipc_service_common_apply_dispatch_result(
148
+ server, dispatch_err, resp_buf_size,
149
+ session->max_response_payload_bytes, true,
150
+ &resp_hdr, &response_len, &close_after_response);
151
+
152
+ /* Send response via the active transport */
153
+ if (shm) {
154
+ size_t msg_len;
155
+ if (!nipc_service_common_header_payload_len(response_len, &msg_len))
156
+ break;
157
+
158
+ resp_hdr.magic = NIPC_MAGIC_MSG;
159
+ resp_hdr.version = NIPC_VERSION;
160
+ resp_hdr.header_len = NIPC_HEADER_LEN;
161
+ resp_hdr.payload_len = (uint32_t)response_len;
162
+
163
+ uint8_t stack_msg[4096];
164
+ uint8_t *msg = (msg_len <= sizeof(stack_msg)) ? stack_msg : malloc(msg_len);
165
+ if (!msg)
166
+ break;
167
+
168
+ nipc_header_encode(&resp_hdr, msg, NIPC_HEADER_LEN);
169
+ if (response_len > 0)
170
+ memcpy(msg + NIPC_HEADER_LEN, resp_buf, response_len);
171
+
172
+ nipc_win_shm_error_t serr = nipc_win_shm_send(shm, msg, msg_len);
173
+ if (msg != stack_msg)
174
+ free(msg);
175
+ if (serr != NIPC_WIN_SHM_OK)
176
+ break;
177
+ } else {
178
+ nipc_np_error_t uerr = nipc_np_send(
179
+ session, &resp_hdr, resp_buf, response_len);
180
+ if (uerr != NIPC_NP_OK)
181
+ break;
182
+ }
183
+
184
+ if (close_after_response)
185
+ break;
186
+ }
187
+
188
+ free(recv_buf);
189
+}
190
+
191
+/* ------------------------------------------------------------------ */
192
+/* Internal: per-session handler thread */
193
+/* ------------------------------------------------------------------ */
194
+
195
+/* Thread function: handles one client session from accept to disconnect. */
196
+unsigned __stdcall nipc_service_win_session_handler_thread(void *arg)
197
+{
198
+ nipc_session_ctx_t *sctx = (nipc_session_ctx_t *)arg;
199
+ nipc_managed_server_t *server = sctx->server;
200
+ /* Allocate a per-session response buffer */
201
+ size_t resp_size = (size_t)sctx->session.max_response_payload_bytes;
202
+ if (resp_size < 1024u)
203
+ resp_size = 1024u;
204
+ uint8_t *resp_buf = nipc_service_win_malloc(
205
+ resp_size, NIPC_WIN_SERVICE_TEST_FAULT_SERVER_RESP_BUF_MALLOC_INTERNAL);
206
+ if (resp_buf) {
207
+ server_handle_session(server, &sctx->session, sctx->shm,
208
+ resp_buf, resp_size);
209
+ free(resp_buf);
210
+ }
211
+
212
+ /* Cleanup SHM and session */
213
+ if (sctx->shm) {
214
+ nipc_win_shm_destroy(sctx->shm);
215
+ free(sctx->shm);
216
+ }
217
+ nipc_np_close_session(&sctx->session);
218
+
219
+ /* Mark inactive; the reap/destroy path owns removal from the array */
220
+ InterlockedExchange((volatile LONG *)&sctx->active, 0);
221
+ return 0;
222
+}
223
+
224
+/* ------------------------------------------------------------------ */
225
+/* Internal: reap finished session threads */
226
+/* ------------------------------------------------------------------ */
227
+
228
+/* Reap all finished (inactive) session threads. Called with lock held. */
229
+void nipc_service_win_server_reap_sessions_locked(nipc_managed_server_t *server)
230
+{
231
+ int i = 0;
232
+ while (i < server->session_count) {
233
+ nipc_session_ctx_t *s = server->sessions[i];
234
+ if (!InterlockedCompareExchange((volatile LONG *)&s->active, 0, 0)) {
235
+ WaitForSingleObject(s->thread, INFINITE);
236
+ CloseHandle(s->thread);
237
+ /* Swap with last, free */
238
+ server->sessions[i] = server->sessions[server->session_count - 1];
239
+ server->session_count--;
240
+ free(s);
241
+ } else {
242
+ i++;
243
+ }
244
+ }
245
+
246
+}
247
+
248
+
249
+#endif /* _WIN32 || __MSYS__ */
src/libnetdata/netipc/src/transport/posix/netipc_shm.c
+149
-43
@@ -11,7 +11,6 @@
11
#include <dirent.h>
12
#include <errno.h>
13
#include <fcntl.h>
14
-#include <inttypes.h>
14
#include <signal.h>
15
#include <stdio.h>
16
#include <stdlib.h>
@@ -36,6 +35,21 @@ static inline uint32_t align64(uint32_t v)
35
return (v + (NIPC_SHM_REGION_ALIGNMENT - 1)) & ~(uint32_t)(NIPC_SHM_REGION_ALIGNMENT - 1);
36
}
37
38
+static int copy_cstr_checked(char *dst, size_t dst_size, const char *src)
39
+{
40
+ if (!dst || !src || dst_size == 0)
41
+ return -1;
42
+
43
+ size_t len = 0;
44
+ while (len < dst_size && src[len] != '\0')
45
+ len++;
46
+ if (len == dst_size)
47
+ return -1;
48
+
49
+ memcpy(dst, src, len + 1);
50
+ return 0;
51
+}
52
+
53
/* Validate service_name: only [a-zA-Z0-9._-], non-empty, not "." or "..". */
54
static int validate_service_name(const char *name)
55
{
@@ -56,21 +70,61 @@ static int validate_service_name(const char *name)
70
return 0;
71
}
72
73
+static int build_shm_name(char *dst, size_t dst_len,
74
+ const char *service_name,
75
+ uint64_t session_id)
76
+{
77
+ if (validate_service_name(service_name) < 0)
78
+ return -2; /* invalid service name */
79
+
80
+ int n = snprintf(dst, dst_len, "%s-%016llx.ipcshm",
81
+ service_name, (unsigned long long)session_id);
82
+ if (n < 0 || (size_t)n >= dst_len)
83
+ return -1;
84
+ return 0;
85
+}
86
+
87
/* Build per-session SHM file path: {run_dir}/{service_name}-{session_id:016x}.ipcshm */
88
static int build_shm_path(char *dst, size_t dst_len,
89
const char *run_dir, const char *service_name,
90
uint64_t session_id)
91
{
64
- if (validate_service_name(service_name) < 0)
65
- return -2; /* invalid service name */
92
+ char shm_name[256];
93
+ int name_rc = build_shm_name(shm_name, sizeof(shm_name), service_name,
94
+ session_id);
95
+ if (name_rc < 0)
96
+ return name_rc;
97
67
- int n = snprintf(dst, dst_len, "%s/%s-%016" PRIx64 ".ipcshm",
68
- run_dir, service_name, session_id);
98
+ int n = snprintf(dst, dst_len, "%s/%s", run_dir, shm_name);
99
if (n < 0 || (size_t)n >= dst_len)
100
return -1;
101
return 0;
102
}
103
104
+static bool dir_fd_allows_stale_unlink(int dir_fd)
105
+{
106
+ struct stat st;
107
+ if (dir_fd < 0 || fstat(dir_fd, &st) != 0)
108
+ return false;
109
+ if (!S_ISDIR(st.st_mode))
110
+ return false;
111
+ if (st.st_uid != geteuid())
112
+ return false;
113
+ return (st.st_mode & (S_IWGRP | S_IWOTH)) == 0;
114
+}
115
+
116
+static int open_run_dir_fd(const char *run_dir)
117
+{
118
+ DIR *dir = opendir(run_dir);
119
+ if (!dir)
120
+ return -1;
121
+
122
+ int raw_fd = dirfd(dir);
123
+ int fd = raw_fd >= 0 ? fcntl(raw_fd, F_DUPFD_CLOEXEC, 0) : -1;
124
+ closedir(dir);
125
+ return fd;
126
+}
127
+
128
/* Thin wrapper around the futex syscall. */
129
static int futex_wake(uint32_t *addr, int count)
130
{
@@ -110,12 +164,6 @@ static inline void *region_ptr(const nipc_shm_ctx_t *ctx, uint32_t offset)
164
return (uint8_t *)ctx->base + offset;
165
}
166
113
-/* Pointer to the header (always at offset 0). Used for non-atomic fields. */
114
-static inline nipc_shm_region_header_t *region_hdr(const nipc_shm_ctx_t *ctx)
115
-{
116
- return (nipc_shm_region_header_t *)ctx->base;
117
-}
118
-
167
/*
168
* Byte-offset accessors for atomic fields. These avoid taking the
169
* address of a packed struct member, which GCC warns about.
@@ -148,27 +196,33 @@ static inline uint32_t *shm_u32_ptr(void *base, int offset)
196
* -1 = doesn't exist
197
* -2 = exists but undersized / invalid (treated as stale, unlinked)
198
*/
151
-static int unlink_same_file(const char *path, const struct stat *expected)
199
+static int unlink_same_file(int dir_fd, const char *name, const struct stat *expected,
200
+ bool allow_stale_unlink)
201
{
202
+ if (!allow_stale_unlink)
203
+ return -1;
204
+
205
struct stat current;
154
- if (lstat(path, ¤t) != 0)
206
+ if (fstatat(dir_fd, name, ¤t, AT_SYMLINK_NOFOLLOW) != 0)
207
return (errno == ENOENT) ? 0 : -1;
208
209
if (current.st_dev != expected->st_dev || current.st_ino != expected->st_ino)
210
return -1;
211
160
- if (unlink(path) == 0 || errno == ENOENT)
212
+ /* Stale recovery only unlinks same-inode entries in a private run directory. */
213
+ if (unlinkat(dir_fd, name, 0) == 0 || errno == ENOENT)
214
return 0;
215
216
return -1;
217
}
218
166
-static int check_shm_stale(const char *path)
219
+static int check_shm_stale(int dir_fd, const char *name,
220
+ bool allow_stale_unlink)
221
{
222
#ifdef O_NOFOLLOW
169
- int fd = open(path, O_RDONLY | O_NOFOLLOW);
223
+ int fd = openat(dir_fd, name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
224
#else
171
- int fd = open(path, O_RDONLY);
225
+ int fd = openat(dir_fd, name, O_RDONLY | O_CLOEXEC);
226
#endif
227
if (fd < 0) {
228
if (errno == ENOENT)
@@ -192,13 +246,13 @@ static int check_shm_stale(const char *path)
246
/* Must be at least header-sized to inspect. */
247
if (st.st_size < (off_t)NIPC_SHM_HEADER_LEN) {
248
close(fd);
195
- return (unlink_same_file(path, &st) == 0) ? -2 : 1;
249
+ return (unlink_same_file(dir_fd, name, &st, allow_stale_unlink) == 0) ? -2 : 1;
250
}
251
252
void *map = mmap(NULL, NIPC_SHM_HEADER_LEN, PROT_READ, MAP_SHARED, fd, 0);
253
close(fd);
254
if (map == MAP_FAILED) {
201
- return (unlink_same_file(path, &st) == 0) ? -2 : 1;
255
+ return (unlink_same_file(dir_fd, name, &st, allow_stale_unlink) == 0) ? -2 : 1;
256
}
257
258
const nipc_shm_region_header_t *hdr = (const nipc_shm_region_header_t *)map;
@@ -206,7 +260,7 @@ static int check_shm_stale(const char *path)
260
/* Validate magic first. */
261
if (hdr->magic != NIPC_SHM_REGION_MAGIC) {
262
munmap(map, NIPC_SHM_HEADER_LEN);
209
- return (unlink_same_file(path, &st) == 0) ? -2 : 1;
263
+ return (unlink_same_file(dir_fd, name, &st, allow_stale_unlink) == 0) ? -2 : 1;
264
}
265
266
int32_t owner = hdr->owner_pid;
@@ -218,7 +272,7 @@ static int check_shm_stale(const char *path)
272
}
273
274
/* Dead owner or zero generation (uninitialized/legacy) — stale */
221
- return (unlink_same_file(path, &st) == 0) ? 0 : 1;
275
+ return (unlink_same_file(dir_fd, name, &st, allow_stale_unlink) == 0) ? 0 : 1;
276
}
277
278
/* ------------------------------------------------------------------ */
@@ -247,37 +301,61 @@ nipc_shm_error_t nipc_shm_server_create(const char *run_dir,
301
if (path_rc < 0)
302
return NIPC_SHM_ERR_PATH_TOO_LONG;
303
304
+ char shm_name[256];
305
+ int name_rc = build_shm_name(shm_name, sizeof(shm_name), service_name,
306
+ session_id);
307
+ if (name_rc == -2)
308
+ return NIPC_SHM_ERR_BAD_PARAM;
309
+ if (name_rc < 0)
310
+ return NIPC_SHM_ERR_PATH_TOO_LONG;
311
+
312
+ int dir_fd = open_run_dir_fd(run_dir);
313
+ if (dir_fd < 0)
314
+ return NIPC_SHM_ERR_OPEN;
315
+
316
/* Round capacities up to alignment. */
317
req_capacity = align64(req_capacity);
318
resp_capacity = align64(resp_capacity);
319
320
uint32_t req_off = align64(NIPC_SHM_HEADER_LEN);
321
/* Guard against uint32 overflow before computing resp_off */
256
- if (req_capacity > UINT32_MAX - req_off)
322
+ if (req_capacity > UINT32_MAX - req_off) {
323
+ close(dir_fd);
324
return NIPC_SHM_ERR_BAD_PARAM;
325
+ }
326
uint32_t resp_off = align64(req_off + req_capacity);
259
- if (resp_capacity > UINT32_MAX - resp_off ||
260
- (size_t)resp_off > SIZE_MAX - (size_t)resp_capacity)
327
+ if (resp_capacity > UINT32_MAX - resp_off) {
328
+ close(dir_fd);
329
return NIPC_SHM_ERR_BAD_PARAM;
330
+ }
331
size_t region_size = (size_t)resp_off + resp_capacity;
332
333
/* Try O_EXCL create first (fast path, no stale check needed). */
265
- int fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
334
+ int fd = openat(dir_fd, shm_name,
335
+ O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
336
337
/* If O_EXCL failed because file exists, do stale recovery and retry. */
338
if (fd < 0 && errno == EEXIST) {
269
- int stale = check_shm_stale(path);
270
- if (stale == 1)
339
+ bool allow_stale_unlink = dir_fd_allows_stale_unlink(dir_fd);
340
+
341
+ int stale = check_shm_stale(dir_fd, shm_name, allow_stale_unlink);
342
+ if (stale == 1) {
343
+ close(dir_fd);
344
return NIPC_SHM_ERR_ADDR_IN_USE;
345
+ }
346
/* Stale file was unlinked, retry create */
273
- fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
347
+ fd = openat(dir_fd, shm_name,
348
+ O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
349
}
275
- if (fd < 0)
350
+ if (fd < 0) {
351
+ close(dir_fd);
352
return NIPC_SHM_ERR_OPEN;
353
+ }
354
355
if (ftruncate(fd, (off_t)region_size) < 0) {
356
close(fd);
280
- unlink(path);
357
+ unlinkat(dir_fd, shm_name, 0);
358
+ close(dir_fd);
359
return NIPC_SHM_ERR_TRUNCATE;
360
}
361
@@ -285,7 +363,8 @@ nipc_shm_error_t nipc_shm_server_create(const char *run_dir,
363
MAP_SHARED, fd, 0);
364
if (map == MAP_FAILED) {
365
close(fd);
288
- unlink(path);
366
+ unlinkat(dir_fd, shm_name, 0);
367
+ close(dir_fd);
368
return NIPC_SHM_ERR_MMAP;
369
}
370
@@ -326,9 +405,17 @@ nipc_shm_error_t nipc_shm_server_create(const char *run_dir,
405
out->local_resp_seq = 0;
406
out->spin_tries = NIPC_SHM_DEFAULT_SPIN;
407
out->owner_generation = hdr->owner_generation;
329
- strncpy(out->path, path, sizeof(out->path) - 1);
330
- out->path[sizeof(out->path) - 1] = '\0';
408
+ if (copy_cstr_checked(out->path, sizeof(out->path), path) != 0) {
409
+ munmap(map, region_size);
410
+ close(fd);
411
+ unlinkat(dir_fd, shm_name, 0);
412
+ close(dir_fd);
413
+ memset(out, 0, sizeof(*out));
414
+ out->fd = -1;
415
+ return NIPC_SHM_ERR_PATH_TOO_LONG;
416
+ }
417
418
+ close(dir_fd);
419
return NIPC_SHM_OK;
420
}
421
@@ -382,8 +469,20 @@ nipc_shm_error_t nipc_shm_client_attach(const char *run_dir,
469
if (path_rc < 0)
470
return NIPC_SHM_ERR_PATH_TOO_LONG;
471
472
+ char shm_name[256];
473
+ int name_rc = build_shm_name(shm_name, sizeof(shm_name), service_name,
474
+ session_id);
475
+ if (name_rc == -2)
476
+ return NIPC_SHM_ERR_BAD_PARAM;
477
+ if (name_rc < 0)
478
+ return NIPC_SHM_ERR_PATH_TOO_LONG;
479
+
480
/* Open the file. */
386
- int fd = open(path, O_RDWR);
481
+ int dir_fd = open_run_dir_fd(run_dir);
482
+ if (dir_fd < 0)
483
+ return NIPC_SHM_ERR_OPEN;
484
+ int fd = openat(dir_fd, shm_name, O_RDWR | O_CLOEXEC);
485
+ close(dir_fd);
486
if (fd < 0)
487
return NIPC_SHM_ERR_OPEN;
488
@@ -493,8 +592,13 @@ nipc_shm_error_t nipc_shm_client_attach(const char *run_dir,
592
out->local_resp_seq = cur_resp_seq;
593
out->spin_tries = NIPC_SHM_DEFAULT_SPIN;
594
out->owner_generation = hdr->owner_generation;
496
- strncpy(out->path, path, sizeof(out->path) - 1);
497
- out->path[sizeof(out->path) - 1] = '\0';
595
+ if (copy_cstr_checked(out->path, sizeof(out->path), path) != 0) {
596
+ munmap(map, file_size);
597
+ close(fd);
598
+ memset(out, 0, sizeof(*out));
599
+ out->fd = -1;
600
+ return NIPC_SHM_ERR_PATH_TOO_LONG;
601
+ }
602
603
return NIPC_SHM_OK;
604
}
@@ -786,6 +890,14 @@ void nipc_shm_cleanup_stale(const char *run_dir, const char *service_name)
890
if (!dir)
891
return;
892
893
+ int dir_fd = dirfd(dir);
894
+ if (dir_fd < 0) {
895
+ closedir(dir);
896
+ return;
897
+ }
898
+
899
+ bool allow_stale_unlink = dir_fd_allows_stale_unlink(dir_fd);
900
+
901
struct dirent *ent;
902
while ((ent = readdir(dir)) != NULL) {
903
size_t nlen = strlen(ent->d_name);
@@ -798,15 +910,9 @@ void nipc_shm_cleanup_stale(const char *run_dir, const char *service_name)
910
if (strcmp(ent->d_name + nlen - suffix_len, suffix) != 0)
911
continue;
912
801
- /* Build full path and check if stale */
802
- char path[512];
803
- int n = snprintf(path, sizeof(path), "%s/%s", run_dir, ent->d_name);
804
- if (n < 0 || (size_t)n >= sizeof(path))
805
- continue;
806
-
913
/* check_shm_stale unlinks stale files and returns:
914
* 0 = stale (unlinked), +1 = live, -1 = gone, -2 = invalid (unlinked) */
809
- check_shm_stale(path);
915
+ check_shm_stale(dir_fd, ent->d_name, allow_stale_unlink);
916
}
917
918
closedir(dir);
src/libnetdata/netipc/src/transport/posix/netipc_uds.c
+19
-1055
@@ -1,189 +1,39 @@
1
/*
2
- * netipc_uds.c - L1 POSIX UDS SEQPACKET transport.
3
- *
4
- * Implements connection lifecycle, handshake with profile/limit negotiation,
5
- * and send/receive with transparent chunking over AF_UNIX SEQPACKET sockets.
2
+ * netipc_uds.c - shared low-level helpers for the POSIX UDS transport.
3
*/
4
8
-#include "netipc/netipc_uds.h"
9
-#include "netipc/netipc_protocol.h"
5
+#include "netipc_uds_internal.h"
6
11
-#include <errno.h>
12
-#include <fcntl.h>
13
-#include <stdlib.h>
14
-#include <stdio.h>
7
+#include <limits.h>
8
#include <string.h>
16
-#include <unistd.h>
17
-
9
#include <sys/socket.h>
19
-#include <sys/stat.h>
20
-#include <sys/un.h>
21
-
22
-/* ------------------------------------------------------------------ */
23
-/* Internal constants */
24
-/* ------------------------------------------------------------------ */
25
-
26
-#define UDS_DEFAULT_BACKLOG 16
27
-#define UDS_DEFAULT_BATCH_ITEMS 1
28
-#define UDS_INITIAL_RECV_BUF 4096
10
30
-/* ------------------------------------------------------------------ */
31
-/* Internal helpers */
32
-/* ------------------------------------------------------------------ */
33
-
34
-static bool header_payload_len(size_t payload_len, size_t *msg_len_out)
11
+bool nipc_uds_header_payload_len(size_t payload_len, size_t *msg_len_out)
12
{
36
-#if SIZE_MAX <= UINT32_MAX
13
if (payload_len > SIZE_MAX - NIPC_HEADER_LEN)
14
return false;
39
-#endif
40
-
41
- *msg_len_out = NIPC_HEADER_LEN + payload_len;
42
- return true;
43
-}
44
-
45
-/* Validate service_name: only [a-zA-Z0-9._-], non-empty, not "." or "..". */
46
-static int validate_service_name(const char *name)
47
-{
48
- if (!name || name[0] == '\0')
49
- return -1;
15
51
- /* Reject "." and ".." */
52
- if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
53
- return -1;
54
-
55
- for (const char *p = name; *p; p++) {
56
- char c = *p;
57
- if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
58
- (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-')
59
- continue;
60
- return -1;
61
- }
62
- return 0;
63
-}
64
-
65
-/* Build socket path into dst, return 0 on success, -1 if too long. */
66
-static int build_socket_path(char *dst, size_t dst_len,
67
- const char *run_dir, const char *service_name)
68
-{
69
- if (validate_service_name(service_name) < 0)
70
- return -2; /* invalid service name */
16
+ size_t msg_len = NIPC_HEADER_LEN + payload_len;
17
+ if (msg_len > UINT32_MAX)
18
+ return false;
19
72
- int n = snprintf(dst, dst_len, "%s/%s.sock", run_dir, service_name);
73
- if (n < 0 || (size_t)n >= dst_len)
74
- return -1; /* path too long */
75
- return 0;
20
+ *msg_len_out = msg_len;
21
+ return true;
22
}
23
78
-/* Get the socket's send buffer size as the packet size. */
79
-static uint32_t detect_packet_size(int fd)
24
+uint32_t nipc_uds_detect_packet_size(int fd)
25
{
26
int val = 0;
27
socklen_t len = sizeof(val);
28
if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &val, &len) < 0)
84
- return 65536; /* safe default */
29
+ return 65536;
30
86
- /* Linux doubles SO_SNDBUF internally; use the value as-is, it
87
- * represents the actual kernel buffer available. Clamp to u32. */
31
if (val <= 0)
32
return 65536;
33
return (uint32_t)val;
34
}
35
93
-/* Highest set bit in a bitmask (0 if empty). */
94
-static uint32_t highest_bit(uint32_t mask)
95
-{
96
- if (mask == 0)
97
- return 0;
98
-
99
- uint32_t bit = 1u << 31;
100
- while (!(mask & bit))
101
- bit >>= 1;
102
- return bit;
103
-}
104
-
105
-static inline uint32_t min_u32(uint32_t a, uint32_t b)
106
-{
107
- return a < b ? a : b;
108
-}
109
-
110
-static inline uint32_t max_u32(uint32_t a, uint32_t b)
111
-{
112
- return a > b ? a : b;
113
-}
114
-
115
-static inline uint32_t apply_default(uint32_t val, uint32_t def)
116
-{
117
- return val == 0 ? def : val;
118
-}
119
-
120
-static nipc_uds_error_t raw_send(int fd, const void *data, size_t len);
121
-
122
-static bool header_version_incompatible(const void *buf, size_t buf_len,
123
- uint16_t expected_code)
124
-{
125
- if (buf_len < NIPC_HEADER_LEN)
126
- return false;
127
-
128
- nipc_header_t hdr;
129
- memcpy(&hdr, buf, sizeof(hdr));
130
- return hdr.magic == NIPC_MAGIC_MSG &&
131
- hdr.version != NIPC_VERSION &&
132
- hdr.header_len == NIPC_HEADER_LEN &&
133
- hdr.kind == NIPC_KIND_CONTROL &&
134
- hdr.code == expected_code;
135
-}
136
-
137
-static bool hello_layout_incompatible(const void *buf, size_t buf_len)
138
-{
139
- if (buf_len < sizeof(uint16_t))
140
- return false;
141
-
142
- nipc_hello_t hello;
143
- memset(&hello, 0, sizeof(hello));
144
- memcpy(&hello, buf, sizeof(uint16_t));
145
- return hello.layout_version != 1;
146
-}
147
-
148
-static bool hello_ack_layout_incompatible(const void *buf, size_t buf_len)
149
-{
150
- if (buf_len < sizeof(uint16_t))
151
- return false;
152
-
153
- nipc_hello_ack_t ack;
154
- memset(&ack, 0, sizeof(ack));
155
- memcpy(&ack, buf, sizeof(uint16_t));
156
- return ack.layout_version != 1;
157
-}
158
-
159
-static void send_rejection_ack(int fd, uint16_t status)
160
-{
161
- nipc_hello_ack_t ack = { .layout_version = 1 };
162
- uint8_t ack_buf[48];
163
- uint8_t pkt[80];
164
- nipc_header_t ack_hdr = {
165
- .magic = NIPC_MAGIC_MSG,
166
- .version = NIPC_VERSION,
167
- .header_len = NIPC_HEADER_LEN,
168
- .kind = NIPC_KIND_CONTROL,
169
- .code = NIPC_CODE_HELLO_ACK,
170
- .transport_status = status,
171
- .payload_len = sizeof(ack_buf),
172
- .item_count = 1,
173
- };
174
-
175
- nipc_hello_ack_encode(&ack, ack_buf, sizeof(ack_buf));
176
- nipc_header_encode(&ack_hdr, pkt, sizeof(pkt));
177
- memcpy(pkt + NIPC_HEADER_LEN, ack_buf, sizeof(ack_buf));
178
- raw_send(fd, pkt, NIPC_HEADER_LEN + sizeof(ack_buf));
179
-}
180
-
181
-/* ------------------------------------------------------------------ */
182
-/* Low-level send/recv (one SEQPACKET datagram) */
183
-/* ------------------------------------------------------------------ */
184
-
185
-/* Send exactly len bytes as one SEQPACKET message. */
186
-static nipc_uds_error_t raw_send(int fd, const void *data, size_t len)
36
+nipc_uds_error_t nipc_uds_raw_send(int fd, const void *data, size_t len)
37
{
38
ssize_t n = send(fd, data, len, MSG_NOSIGNAL);
39
if (n < 0 || (size_t)n != len)
@@ -191,9 +41,8 @@ static nipc_uds_error_t raw_send(int fd, const void *data, size_t len)
41
return NIPC_UDS_OK;
42
}
43
194
-/* Send header + payload as one SEQPACKET message using sendmsg. */
195
-static nipc_uds_error_t raw_send_iov(int fd, const void *hdr, size_t hdr_len,
196
- const void *payload, size_t payload_len)
44
+nipc_uds_error_t nipc_uds_raw_send_iov(int fd, const void *hdr, size_t hdr_len,
45
+ const void *payload, size_t payload_len)
46
{
47
struct iovec iov[2];
48
struct msghdr msg;
@@ -202,16 +51,16 @@ static nipc_uds_error_t raw_send_iov(int fd, const void *hdr, size_t hdr_len,
51
memset(&msg, 0, sizeof(msg));
52
53
iov[0].iov_base = (void *)hdr;
205
- iov[0].iov_len = hdr_len;
54
+ iov[0].iov_len = hdr_len;
55
iovcnt = 1;
56
57
if (payload && payload_len > 0) {
58
iov[1].iov_base = (void *)payload;
210
- iov[1].iov_len = payload_len;
59
+ iov[1].iov_len = payload_len;
60
iovcnt = 2;
61
}
62
214
- msg.msg_iov = iov;
63
+ msg.msg_iov = iov;
64
msg.msg_iovlen = iovcnt;
65
66
size_t total = hdr_len + payload_len;
@@ -222,892 +71,7 @@ static nipc_uds_error_t raw_send_iov(int fd, const void *hdr, size_t hdr_len,
71
return NIPC_UDS_OK;
72
}
73
225
-/* Receive one SEQPACKET message into buf. Returns bytes received, 0 on
226
- * disconnect, -1 on error. */
227
-static ssize_t raw_recv(int fd, void *buf, size_t buf_len)
228
-{
229
- ssize_t n = recv(fd, buf, buf_len, 0);
230
- return n;
231
-}
232
-
233
-/* ------------------------------------------------------------------ */
234
-/* Handshake: client side */
235
-/* ------------------------------------------------------------------ */
236
-
237
-static nipc_uds_error_t client_handshake(int fd,
238
- const nipc_uds_client_config_t *cfg,
239
- nipc_uds_session_t *session)
240
-{
241
- uint8_t buf[128]; /* enough for header(32) + hello(44) = 76, and ack */
242
- nipc_uds_error_t err;
243
-
244
- /* Detect packet size if not specified */
245
- uint32_t pkt_size = cfg->packet_size;
246
- if (pkt_size == 0)
247
- pkt_size = detect_packet_size(fd);
248
-
249
- /* Build HELLO payload */
250
- nipc_hello_t hello = {
251
- .layout_version = 1,
252
- .flags = 0,
253
- .supported_profiles = cfg->supported_profiles ? cfg->supported_profiles : NIPC_PROFILE_BASELINE,
254
- .preferred_profiles = cfg->preferred_profiles,
255
- .max_request_payload_bytes = apply_default(cfg->max_request_payload_bytes, NIPC_MAX_PAYLOAD_DEFAULT),
256
- .max_request_batch_items = apply_default(cfg->max_request_batch_items, UDS_DEFAULT_BATCH_ITEMS),
257
- .max_response_payload_bytes = apply_default(cfg->max_response_payload_bytes, NIPC_MAX_PAYLOAD_DEFAULT),
258
- .max_response_batch_items = apply_default(cfg->max_response_batch_items, UDS_DEFAULT_BATCH_ITEMS),
259
- .auth_token = cfg->auth_token,
260
- .packet_size = pkt_size,
261
- };
262
-
263
- uint8_t hello_buf[44];
264
- nipc_hello_encode(&hello, hello_buf, sizeof(hello_buf));
265
-
266
- /* Build outer CONTROL header */
267
- nipc_header_t hdr = {
268
- .magic = NIPC_MAGIC_MSG,
269
- .version = NIPC_VERSION,
270
- .header_len = NIPC_HEADER_LEN,
271
- .kind = NIPC_KIND_CONTROL,
272
- .flags = 0,
273
- .code = NIPC_CODE_HELLO,
274
- .transport_status = NIPC_STATUS_OK,
275
- .payload_len = sizeof(hello_buf),
276
- .item_count = 1,
277
- .message_id = 0,
278
- };
279
-
280
- nipc_header_encode(&hdr, buf, sizeof(buf));
281
- memcpy(buf + NIPC_HEADER_LEN, hello_buf, sizeof(hello_buf));
282
-
283
- /* Send HELLO */
284
- err = raw_send(fd, buf, NIPC_HEADER_LEN + sizeof(hello_buf));
285
- if (err != NIPC_UDS_OK)
286
- return err;
287
-
288
- /* Receive HELLO_ACK */
289
- ssize_t n = raw_recv(fd, buf, sizeof(buf));
290
- if (n <= 0)
291
- return NIPC_UDS_ERR_RECV;
292
-
293
- /* Decode outer header */
294
- nipc_header_t ack_hdr;
295
- nipc_error_t perr = nipc_header_decode(buf, (size_t)n, &ack_hdr);
296
- if (perr == NIPC_ERR_BAD_VERSION)
297
- return NIPC_UDS_ERR_INCOMPATIBLE;
298
- if (perr != NIPC_OK)
299
- return NIPC_UDS_ERR_PROTOCOL;
300
-
301
- if (ack_hdr.kind != NIPC_KIND_CONTROL || ack_hdr.code != NIPC_CODE_HELLO_ACK)
302
- return NIPC_UDS_ERR_PROTOCOL;
303
-
304
- /* Check transport_status for rejection */
305
- if (ack_hdr.transport_status == NIPC_STATUS_AUTH_FAILED)
306
- return NIPC_UDS_ERR_AUTH_FAILED;
307
- if (ack_hdr.transport_status == NIPC_STATUS_UNSUPPORTED)
308
- return NIPC_UDS_ERR_NO_PROFILE;
309
- if (ack_hdr.transport_status == NIPC_STATUS_INCOMPATIBLE)
310
- return NIPC_UDS_ERR_INCOMPATIBLE;
311
- if (ack_hdr.transport_status == NIPC_STATUS_LIMIT_EXCEEDED)
312
- return NIPC_UDS_ERR_LIMIT_EXCEEDED;
313
- if (ack_hdr.transport_status != NIPC_STATUS_OK)
314
- return NIPC_UDS_ERR_HANDSHAKE;
315
-
316
- /* Decode hello-ack payload */
317
- nipc_hello_ack_t ack;
318
- perr = nipc_hello_ack_decode(buf + NIPC_HEADER_LEN,
319
- (size_t)n - NIPC_HEADER_LEN, &ack);
320
- if (perr == NIPC_ERR_BAD_LAYOUT &&
321
- hello_ack_layout_incompatible(buf + NIPC_HEADER_LEN,
322
- (size_t)n - NIPC_HEADER_LEN))
323
- return NIPC_UDS_ERR_INCOMPATIBLE;
324
- if (perr != NIPC_OK)
325
- return NIPC_UDS_ERR_PROTOCOL;
326
-
327
- /* Fill session */
328
- session->fd = fd;
329
- session->role = NIPC_UDS_ROLE_CLIENT;
330
- session->max_request_payload_bytes = ack.agreed_max_request_payload_bytes;
331
- session->max_request_batch_items = ack.agreed_max_request_batch_items;
332
- session->max_response_payload_bytes = ack.agreed_max_response_payload_bytes;
333
- session->max_response_batch_items = ack.agreed_max_response_batch_items;
334
- session->packet_size = ack.agreed_packet_size;
335
- session->selected_profile = ack.selected_profile;
336
- session->session_id = ack.session_id;
337
- session->recv_buf = NULL;
338
- session->recv_buf_size = 0;
339
-
340
- /* Sanity: reject a packet_size too small for chunking arithmetic */
341
- if (session->packet_size <= NIPC_HEADER_LEN)
342
- return NIPC_UDS_ERR_PROTOCOL;
343
-
344
- return NIPC_UDS_OK;
345
-}
346
-
347
-/* ------------------------------------------------------------------ */
348
-/* Handshake: server side */
349
-/* ------------------------------------------------------------------ */
350
-
351
-static nipc_uds_error_t server_handshake(int fd,
352
- const nipc_uds_server_config_t *cfg,
353
- uint64_t session_id,
354
- nipc_uds_session_t *session)
74
+ssize_t nipc_uds_raw_recv(int fd, void *buf, size_t buf_len)
75
{
356
- uint8_t buf[128];
357
-
358
- /* Detect server packet size */
359
- uint32_t server_pkt_size = cfg->packet_size;
360
- if (server_pkt_size == 0)
361
- server_pkt_size = detect_packet_size(fd);
362
-
363
- /* Server limits with defaults applied */
364
- uint32_t s_req_pay = apply_default(cfg->max_request_payload_bytes, NIPC_MAX_PAYLOAD_DEFAULT);
365
- uint32_t s_req_bat = apply_default(cfg->max_request_batch_items, UDS_DEFAULT_BATCH_ITEMS);
366
- uint32_t s_resp_pay = apply_default(cfg->max_response_payload_bytes, NIPC_MAX_PAYLOAD_DEFAULT);
367
- uint32_t s_resp_bat = apply_default(cfg->max_response_batch_items, UDS_DEFAULT_BATCH_ITEMS);
368
- uint32_t s_profiles = cfg->supported_profiles ? cfg->supported_profiles : NIPC_PROFILE_BASELINE;
369
- uint32_t s_preferred = cfg->preferred_profiles;
370
-
371
- /* Receive HELLO */
372
- ssize_t n = raw_recv(fd, buf, sizeof(buf));
373
- if (n <= 0)
374
- return NIPC_UDS_ERR_RECV;
375
-
376
- nipc_header_t hdr;
377
- nipc_error_t perr = nipc_header_decode(buf, (size_t)n, &hdr);
378
- if (perr == NIPC_ERR_BAD_VERSION &&
379
- header_version_incompatible(buf, (size_t)n, NIPC_CODE_HELLO)) {
380
- send_rejection_ack(fd, NIPC_STATUS_INCOMPATIBLE);
381
- return NIPC_UDS_ERR_INCOMPATIBLE;
382
- }
383
- if (perr != NIPC_OK)
384
- return NIPC_UDS_ERR_PROTOCOL;
385
-
386
- if (hdr.kind != NIPC_KIND_CONTROL || hdr.code != NIPC_CODE_HELLO)
387
- return NIPC_UDS_ERR_PROTOCOL;
388
-
389
- nipc_hello_t hello;
390
- perr = nipc_hello_decode(buf + NIPC_HEADER_LEN,
391
- (size_t)n - NIPC_HEADER_LEN, &hello);
392
- if (perr == NIPC_ERR_BAD_LAYOUT &&
393
- hello_layout_incompatible(buf + NIPC_HEADER_LEN,
394
- (size_t)n - NIPC_HEADER_LEN)) {
395
- send_rejection_ack(fd, NIPC_STATUS_INCOMPATIBLE);
396
- return NIPC_UDS_ERR_INCOMPATIBLE;
397
- }
398
- if (perr != NIPC_OK)
399
- return NIPC_UDS_ERR_PROTOCOL;
400
-
401
- /* Compute intersection */
402
- uint32_t intersection = hello.supported_profiles & s_profiles;
403
-
404
- /* Check intersection */
405
- if (intersection == 0) {
406
- send_rejection_ack(fd, NIPC_STATUS_UNSUPPORTED);
407
- return NIPC_UDS_ERR_NO_PROFILE;
408
- }
409
-
410
- /* Check auth */
411
- if (hello.auth_token != cfg->auth_token) {
412
- send_rejection_ack(fd, NIPC_STATUS_AUTH_FAILED);
413
- return NIPC_UDS_ERR_AUTH_FAILED;
414
- }
415
-
416
- /* Select profile: prefer preferred_intersection, then intersection */
417
- uint32_t preferred_intersection = intersection &
418
- hello.preferred_profiles & s_preferred;
419
- uint32_t selected;
420
- if (preferred_intersection != 0)
421
- selected = highest_bit(preferred_intersection);
422
- else
423
- selected = highest_bit(intersection);
424
-
425
- if (hello.max_request_payload_bytes > NIPC_MAX_PAYLOAD_CAP) {
426
- send_rejection_ack(fd, NIPC_STATUS_LIMIT_EXCEEDED);
427
- return NIPC_UDS_ERR_LIMIT_EXCEEDED;
428
- }
429
-
430
- /* Negotiate limits:
431
- * - request payload and batch size are client-proposed and echoed
432
- * - response payload is server-authoritative
433
- * - response batch size is symmetric with request batch size */
434
- uint32_t agreed_req_pay = hello.max_request_payload_bytes;
435
- uint32_t agreed_req_bat = hello.max_request_batch_items;
436
- uint32_t agreed_resp_pay = s_resp_pay;
437
- uint32_t agreed_resp_bat = agreed_req_bat;
438
- uint32_t agreed_pkt = min_u32(hello.packet_size, server_pkt_size);
439
-
440
- /* packet_size must be large enough for a usable message packet */
441
- if (agreed_pkt <= NIPC_HEADER_LEN) {
442
- send_rejection_ack(fd, NIPC_STATUS_INCOMPATIBLE);
443
- return NIPC_UDS_ERR_INCOMPATIBLE;
444
- }
445
-
446
- /* Send HELLO_ACK (success) */
447
- nipc_hello_ack_t ack = {
448
- .layout_version = 1,
449
- .flags = 0,
450
- .server_supported_profiles = s_profiles,
451
- .intersection_profiles = intersection,
452
- .selected_profile = selected,
453
- .agreed_max_request_payload_bytes = agreed_req_pay,
454
- .agreed_max_request_batch_items = agreed_req_bat,
455
- .agreed_max_response_payload_bytes = agreed_resp_pay,
456
- .agreed_max_response_batch_items = agreed_resp_bat,
457
- .agreed_packet_size = agreed_pkt,
458
- .session_id = session_id,
459
- };
460
-
461
- uint8_t ack_buf[48];
462
- nipc_hello_ack_encode(&ack, ack_buf, sizeof(ack_buf));
463
-
464
- nipc_header_t ack_hdr = {
465
- .magic = NIPC_MAGIC_MSG,
466
- .version = NIPC_VERSION,
467
- .header_len = NIPC_HEADER_LEN,
468
- .kind = NIPC_KIND_CONTROL,
469
- .flags = 0,
470
- .code = NIPC_CODE_HELLO_ACK,
471
- .transport_status = NIPC_STATUS_OK,
472
- .payload_len = sizeof(ack_buf),
473
- .item_count = 1,
474
- .message_id = 0,
475
- };
476
-
477
- uint8_t pkt[80];
478
- nipc_header_encode(&ack_hdr, pkt, sizeof(pkt));
479
- memcpy(pkt + NIPC_HEADER_LEN, ack_buf, sizeof(ack_buf));
480
-
481
- nipc_uds_error_t send_ack_err = raw_send(fd, pkt, NIPC_HEADER_LEN + sizeof(ack_buf));
482
- if (send_ack_err != NIPC_UDS_OK)
483
- return send_ack_err;
484
-
485
- /* Fill session */
486
- session->fd = fd;
487
- session->role = NIPC_UDS_ROLE_SERVER;
488
- session->max_request_payload_bytes = agreed_req_pay;
489
- session->max_request_batch_items = agreed_req_bat;
490
- session->max_response_payload_bytes = agreed_resp_pay;
491
- session->max_response_batch_items = agreed_resp_bat;
492
- session->packet_size = agreed_pkt;
493
- session->selected_profile = selected;
494
- session->session_id = session_id;
495
- session->recv_buf = NULL;
496
- session->recv_buf_size = 0;
497
-
498
- return NIPC_UDS_OK;
499
-}
500
-
501
-/* ------------------------------------------------------------------ */
502
-/* Stale endpoint recovery */
503
-/* ------------------------------------------------------------------ */
504
-
505
-static int unlink_stale_socket_path(const char *path)
506
-{
507
- struct stat st;
508
- if (lstat(path, &st) != 0)
509
- return (errno == ENOENT) ? 0 : -1;
510
-
511
- if (!S_ISSOCK(st.st_mode))
512
- return -1;
513
-
514
- if (unlink(path) == 0 || errno == ENOENT)
515
- return 0;
516
-
517
- return -1;
518
-}
519
-
520
-/* Returns: 0 = stale (unlinked), 1 = live server, -1 = doesn't exist */
521
-static int check_and_recover_stale(const char *path)
522
-{
523
- /* Try connecting to check if a live server is there */
524
- int probe = socket(AF_UNIX, SOCK_SEQPACKET, 0);
525
- if (probe < 0)
526
- return -1;
527
-
528
- struct sockaddr_un addr;
529
- memset(&addr, 0, sizeof(addr));
530
- addr.sun_family = AF_UNIX;
531
- strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
532
-
533
- int ret;
534
- if (connect(probe, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
535
- /* Connected -> live server */
536
- close(probe);
537
- ret = 1;
538
- } else {
539
- int saved_errno = errno;
540
- close(probe);
541
- /* Only ECONNREFUSED proves a stale socket path. Other errors
542
- * (including regular files at the path) must not remove anything. */
543
- if (saved_errno == ENOENT) {
544
- ret = -1;
545
- } else if (saved_errno == ECONNREFUSED) {
546
- ret = (unlink_stale_socket_path(path) == 0) ? 0 : 1;
547
- } else {
548
- /* Can't determine ownership — treat as live to prevent overwriting */
549
- ret = 1;
550
- }
551
- }
552
- return ret;
553
-}
554
-
555
-/* ------------------------------------------------------------------ */
556
-/* Public API: listen */
557
-/* ------------------------------------------------------------------ */
558
-
559
-nipc_uds_error_t nipc_uds_listen(const char *run_dir,
560
- const char *service_name,
561
- const nipc_uds_server_config_t *config,
562
- nipc_uds_listener_t *out)
563
-{
564
- memset(out, 0, sizeof(*out));
565
- out->fd = -1;
566
-
567
- /* Build path */
568
- char path[sizeof(((struct sockaddr_un *)0)->sun_path)];
569
- int path_rc = build_socket_path(path, sizeof(path), run_dir, service_name);
570
- if (path_rc == -2)
571
- return NIPC_UDS_ERR_BAD_PARAM;
572
- if (path_rc < 0)
573
- return NIPC_UDS_ERR_PATH_TOO_LONG;
574
-
575
- /* Stale recovery */
576
- int stale = check_and_recover_stale(path);
577
- if (stale == 1)
578
- return NIPC_UDS_ERR_ADDR_IN_USE;
579
-
580
- /* Create socket */
581
- int fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
582
- if (fd < 0)
583
- return NIPC_UDS_ERR_SOCKET;
584
-
585
- struct sockaddr_un addr;
586
- memset(&addr, 0, sizeof(addr));
587
- addr.sun_family = AF_UNIX;
588
- strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
589
-
590
- if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
591
- close(fd);
592
- return NIPC_UDS_ERR_SOCKET;
593
- }
594
-
595
- int backlog = config->backlog > 0 ? config->backlog : UDS_DEFAULT_BACKLOG;
596
- if (listen(fd, backlog) < 0) {
597
- close(fd);
598
- unlink(path);
599
- return NIPC_UDS_ERR_SOCKET;
600
- }
601
-
602
- out->fd = fd;
603
- out->config = *config;
604
- strncpy(out->path, path, sizeof(out->path) - 1);
605
- out->path[sizeof(out->path) - 1] = '\0';
606
-
607
- return NIPC_UDS_OK;
608
-}
609
-
610
-/* ------------------------------------------------------------------ */
611
-/* Public API: accept */
612
-/* ------------------------------------------------------------------ */
613
-
614
-nipc_uds_error_t nipc_uds_accept(nipc_uds_listener_t *listener,
615
- uint64_t session_id,
616
- nipc_uds_session_t *out)
617
-{
618
- memset(out, 0, sizeof(*out));
619
- out->fd = -1;
620
-
621
- int client_fd = accept(listener->fd, NULL, NULL);
622
- if (client_fd < 0)
623
- return NIPC_UDS_ERR_ACCEPT;
624
-
625
- nipc_uds_error_t err = server_handshake(client_fd, &listener->config,
626
- session_id, out);
627
- if (err != NIPC_UDS_OK) {
628
- close(client_fd);
629
- out->fd = -1;
630
- return err;
631
- }
632
-
633
- return NIPC_UDS_OK;
634
-}
635
-
636
-/* ------------------------------------------------------------------ */
637
-/* Public API: connect */
638
-/* ------------------------------------------------------------------ */
639
-
640
-nipc_uds_error_t nipc_uds_connect(const char *run_dir,
641
- const char *service_name,
642
- const nipc_uds_client_config_t *config,
643
- nipc_uds_session_t *out)
644
-{
645
- memset(out, 0, sizeof(*out));
646
- out->fd = -1;
647
-
648
- /* Build path */
649
- char path[sizeof(((struct sockaddr_un *)0)->sun_path)];
650
- int path_rc2 = build_socket_path(path, sizeof(path), run_dir, service_name);
651
- if (path_rc2 == -2)
652
- return NIPC_UDS_ERR_BAD_PARAM;
653
- if (path_rc2 < 0)
654
- return NIPC_UDS_ERR_PATH_TOO_LONG;
655
-
656
- /* Create socket */
657
- int fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
658
- if (fd < 0)
659
- return NIPC_UDS_ERR_SOCKET;
660
-
661
- struct sockaddr_un addr;
662
- memset(&addr, 0, sizeof(addr));
663
- addr.sun_family = AF_UNIX;
664
- strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
665
-
666
- if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
667
- close(fd);
668
- return NIPC_UDS_ERR_CONNECT;
669
- }
670
-
671
- nipc_uds_error_t err = client_handshake(fd, config, out);
672
- if (err != NIPC_UDS_OK) {
673
- close(fd);
674
- out->fd = -1;
675
- return err;
676
- }
677
-
678
- return NIPC_UDS_OK;
679
-}
680
-
681
-/* ------------------------------------------------------------------ */
682
-/* Public API: close */
683
-/* ------------------------------------------------------------------ */
684
-
685
-void nipc_uds_close_session(nipc_uds_session_t *session)
686
-{
687
- if (!session)
688
- return;
689
-
690
- if (session->fd >= 0) {
691
- close(session->fd);
692
- session->fd = -1;
693
- }
694
-
695
- free(session->recv_buf);
696
- session->recv_buf = NULL;
697
- session->recv_buf_size = 0;
698
-
699
- free(session->inflight_ids);
700
- session->inflight_ids = NULL;
701
- session->inflight_count = 0;
702
- session->inflight_capacity = 0;
703
-}
704
-
705
-void nipc_uds_close_listener(nipc_uds_listener_t *listener)
706
-{
707
- if (!listener)
708
- return;
709
-
710
- if (listener->fd >= 0) {
711
- close(listener->fd);
712
- listener->fd = -1;
713
- }
714
-
715
- if (listener->path[0]) {
716
- unlink(listener->path);
717
- listener->path[0] = '\0';
718
- }
719
-}
720
-
721
-/* ------------------------------------------------------------------ */
722
-/* In-flight message_id tracking (client side) */
723
-/* ------------------------------------------------------------------ */
724
-
725
-/* Add message_id to in-flight set. Returns 0 on success, -1 if duplicate,
726
- * -2 on allocation failure. */
727
-static int inflight_add(nipc_uds_session_t *s, uint64_t id)
728
-{
729
- for (uint32_t i = 0; i < s->inflight_count; i++) {
730
- if (s->inflight_ids[i] == id)
731
- return -1; /* duplicate */
732
- }
733
- if (s->inflight_count >= s->inflight_capacity) {
734
- uint32_t new_cap = s->inflight_capacity ? s->inflight_capacity * 2 : 16;
735
- uint64_t *new_ids = realloc(s->inflight_ids, (size_t)new_cap * sizeof(uint64_t));
736
- if (!new_ids)
737
- return -2; /* allocation failure */
738
- s->inflight_ids = new_ids;
739
- s->inflight_capacity = new_cap;
740
- }
741
- s->inflight_ids[s->inflight_count++] = id;
742
- return 0;
743
-}
744
-
745
-/* Remove message_id from in-flight set. Returns 0 on success, -1 if
746
- * not found. */
747
-static int inflight_remove(nipc_uds_session_t *s, uint64_t id)
748
-{
749
- for (uint32_t i = 0; i < s->inflight_count; i++) {
750
- if (s->inflight_ids[i] == id) {
751
- /* Swap with last */
752
- s->inflight_ids[i] = s->inflight_ids[s->inflight_count - 1];
753
- s->inflight_count--;
754
- return 0;
755
- }
756
- }
757
- return -1; /* not found */
758
-}
759
-
760
-static void inflight_fail_all(nipc_uds_session_t *s)
761
-{
762
- if (!s || s->role != NIPC_UDS_ROLE_CLIENT)
763
- return;
764
-
765
- /* A broken session invalidates every in-flight request on it. */
766
- s->inflight_count = 0;
767
-}
768
-
769
-/* ------------------------------------------------------------------ */
770
-/* Public API: send */
771
-/* ------------------------------------------------------------------ */
772
-
773
-nipc_uds_error_t nipc_uds_send(nipc_uds_session_t *session,
774
- nipc_header_t *hdr,
775
- const void *payload,
776
- size_t payload_len)
777
-{
778
- if (!session || session->fd < 0)
779
- return NIPC_UDS_ERR_BAD_PARAM;
780
-
781
- int tracked = (session->role == NIPC_UDS_ROLE_CLIENT &&
782
- hdr->kind == NIPC_KIND_REQUEST);
783
-
784
- /* Client-side: track in-flight message_ids for requests */
785
- if (tracked) {
786
- int rc = inflight_add(session, hdr->message_id);
787
- if (rc == -1)
788
- return NIPC_UDS_ERR_DUPLICATE_MSG_ID;
789
- if (rc == -2)
790
- return NIPC_UDS_ERR_LIMIT_EXCEEDED;
791
- }
792
-
793
- uint32_t max_payload = 0;
794
- uint32_t max_batch = 0;
795
- if (session->role == NIPC_UDS_ROLE_CLIENT &&
796
- hdr->kind == NIPC_KIND_REQUEST) {
797
- max_payload = session->max_request_payload_bytes;
798
- max_batch = session->max_request_batch_items;
799
- } else if (session->role == NIPC_UDS_ROLE_SERVER &&
800
- hdr->kind == NIPC_KIND_RESPONSE) {
801
- max_payload = session->max_response_payload_bytes;
802
- max_batch = session->max_response_batch_items;
803
- }
804
- if (payload_len > UINT32_MAX ||
805
- (max_payload > 0 && payload_len > max_payload) ||
806
- (max_batch > 0 && hdr->item_count > max_batch)) {
807
- if (tracked)
808
- inflight_remove(session, hdr->message_id);
809
- return NIPC_UDS_ERR_LIMIT_EXCEEDED;
810
- }
811
-
812
- /* Fill envelope fields the caller shouldn't set */
813
- hdr->magic = NIPC_MAGIC_MSG;
814
- hdr->version = NIPC_VERSION;
815
- hdr->header_len = NIPC_HEADER_LEN;
816
- hdr->payload_len = (uint32_t)payload_len;
817
-
818
- size_t total_msg;
819
- if (!header_payload_len(payload_len, &total_msg)) {
820
- if (tracked)
821
- inflight_remove(session, hdr->message_id);
822
- return NIPC_UDS_ERR_LIMIT_EXCEEDED;
823
- }
824
-
825
- /* Does it fit in one packet? */
826
- if (total_msg <= session->packet_size) {
827
- /* Single packet: encode header then send header+payload together */
828
- uint8_t hdr_buf[NIPC_HEADER_LEN];
829
- nipc_header_encode(hdr, hdr_buf, sizeof(hdr_buf));
830
- nipc_uds_error_t send_err = raw_send_iov(session->fd, hdr_buf, NIPC_HEADER_LEN,
831
- payload, payload_len);
832
- if (send_err != NIPC_UDS_OK) {
833
- if (session->role == NIPC_UDS_ROLE_CLIENT &&
834
- hdr->kind == NIPC_KIND_REQUEST) {
835
- if (send_err == NIPC_UDS_ERR_SEND)
836
- inflight_fail_all(session);
837
- else
838
- inflight_remove(session, hdr->message_id);
839
- }
840
- }
841
- return send_err;
842
- }
843
-
844
- /* Chunked send */
845
- size_t chunk_payload_budget = session->packet_size - NIPC_HEADER_LEN;
846
- if (chunk_payload_budget == 0)
847
- return NIPC_UDS_ERR_BAD_PARAM;
848
-
849
- /* Calculate chunk count:
850
- * First chunk: header(32) + up to chunk_payload_budget payload bytes.
851
- * But the first chunk carries the outer header, so payload in first
852
- * chunk = chunk_payload_budget. */
853
- size_t remaining = payload_len;
854
- size_t first_chunk_payload = remaining < chunk_payload_budget
855
- ? remaining : chunk_payload_budget;
856
-
857
- remaining -= first_chunk_payload;
858
- uint32_t continuation_chunks = 0;
859
- if (remaining > 0) {
860
- continuation_chunks = (uint32_t)((remaining + chunk_payload_budget - 1)
861
- / chunk_payload_budget);
862
- }
863
- uint32_t chunk_count = 1 + continuation_chunks;
864
-
865
- /* Send first chunk: outer header + first part of payload */
866
- uint8_t hdr_buf[NIPC_HEADER_LEN];
867
- nipc_header_encode(hdr, hdr_buf, sizeof(hdr_buf));
868
-
869
- nipc_uds_error_t err = raw_send_iov(session->fd, hdr_buf, NIPC_HEADER_LEN,
870
- payload, first_chunk_payload);
871
- if (err != NIPC_UDS_OK) {
872
- if (session->role == NIPC_UDS_ROLE_CLIENT &&
873
- hdr->kind == NIPC_KIND_REQUEST) {
874
- if (err == NIPC_UDS_ERR_SEND)
875
- inflight_fail_all(session);
876
- else
877
- inflight_remove(session, hdr->message_id);
878
- }
879
- return err;
880
- }
881
-
882
- /* Send continuation chunks */
883
- const uint8_t *src = (const uint8_t *)payload + first_chunk_payload;
884
- remaining = payload_len - first_chunk_payload;
885
-
886
- for (uint32_t ci = 1; ci < chunk_count; ci++) {
887
- size_t this_chunk = remaining < chunk_payload_budget
888
- ? remaining : chunk_payload_budget;
889
-
890
- nipc_chunk_header_t chk = {
891
- .magic = NIPC_MAGIC_CHUNK,
892
- .version = NIPC_VERSION,
893
- .flags = 0,
894
- .message_id = hdr->message_id,
895
- .total_message_len = (uint32_t)total_msg,
896
- .chunk_index = ci,
897
- .chunk_count = chunk_count,
898
- .chunk_payload_len = (uint32_t)this_chunk,
899
- };
900
-
901
- uint8_t chk_buf[NIPC_HEADER_LEN];
902
- nipc_chunk_header_encode(&chk, chk_buf, sizeof(chk_buf));
903
-
904
- err = raw_send_iov(session->fd, chk_buf, NIPC_HEADER_LEN,
905
- src, this_chunk);
906
- if (err != NIPC_UDS_OK) {
907
- if (session->role == NIPC_UDS_ROLE_CLIENT &&
908
- hdr->kind == NIPC_KIND_REQUEST) {
909
- if (err == NIPC_UDS_ERR_SEND)
910
- inflight_fail_all(session);
911
- else
912
- inflight_remove(session, hdr->message_id);
913
- }
914
- return err;
915
- }
916
-
917
- src += this_chunk;
918
- remaining -= this_chunk;
919
- }
920
-
921
- return NIPC_UDS_OK;
922
-}
923
-
924
-/* ------------------------------------------------------------------ */
925
-/* Public API: receive */
926
-/* ------------------------------------------------------------------ */
927
-
928
-/* Ensure session recv_buf can hold at least `needed` bytes. */
929
-static nipc_uds_error_t ensure_recv_buf(nipc_uds_session_t *session,
930
- size_t needed)
931
-{
932
- if (session->recv_buf_size >= needed)
933
- return NIPC_UDS_OK;
934
-
935
- uint8_t *p = realloc(session->recv_buf, needed);
936
- if (!p)
937
- return NIPC_UDS_ERR_ALLOC;
938
-
939
- session->recv_buf = p;
940
- session->recv_buf_size = needed;
941
- return NIPC_UDS_OK;
942
-}
943
-
944
-/* Validate batch directory if the message has BATCH flag and item_count > 1.
945
- * Called after the full payload is assembled, before returning to caller. */
946
-static nipc_uds_error_t validate_batch(const nipc_header_t *hdr,
947
- const void *payload, size_t payload_len)
948
-{
949
- if (!(hdr->flags & NIPC_FLAG_BATCH) || hdr->item_count <= 1)
950
- return NIPC_UDS_OK;
951
-
952
- uint32_t dir_bytes = hdr->item_count * 8;
953
- uint32_t dir_aligned = (uint32_t)nipc_align8(dir_bytes);
954
- if (payload_len < dir_aligned)
955
- return NIPC_UDS_ERR_PROTOCOL;
956
-
957
- uint32_t packed_area_len = (uint32_t)(payload_len - dir_aligned);
958
- nipc_error_t perr = nipc_batch_dir_validate(payload, dir_bytes,
959
- hdr->item_count,
960
- packed_area_len);
961
- return (perr == NIPC_OK) ? NIPC_UDS_OK : NIPC_UDS_ERR_PROTOCOL;
962
-}
963
-
964
-nipc_uds_error_t nipc_uds_receive(nipc_uds_session_t *session,
965
- void *buf, size_t buf_size,
966
- nipc_header_t *hdr_out,
967
- const void **payload_out,
968
- size_t *payload_len_out)
969
-{
970
- if (!session || session->fd < 0)
971
- return NIPC_UDS_ERR_BAD_PARAM;
972
-
973
- /* Read first packet into the caller's buffer */
974
- ssize_t n = raw_recv(session->fd, buf, buf_size);
975
- if (n <= 0) {
976
- inflight_fail_all(session);
977
- return NIPC_UDS_ERR_RECV;
978
- }
979
-
980
- if ((size_t)n < NIPC_HEADER_LEN)
981
- return NIPC_UDS_ERR_PROTOCOL;
982
-
983
- /* Decode outer header */
984
- nipc_error_t perr = nipc_header_decode(buf, (size_t)n, hdr_out);
985
- if (perr != NIPC_OK)
986
- return NIPC_UDS_ERR_PROTOCOL;
987
-
988
- /* Validate payload_len against negotiated directional limit.
989
- * Server receives requests; client receives responses. */
990
- uint32_t max_payload = (session->role == NIPC_UDS_ROLE_SERVER)
991
- ? session->max_request_payload_bytes
992
- : session->max_response_payload_bytes;
993
- if (hdr_out->payload_len > max_payload)
994
- return NIPC_UDS_ERR_LIMIT_EXCEEDED;
995
-
996
- /* Validate item_count against negotiated directional batch limit. */
997
- uint32_t max_batch = (session->role == NIPC_UDS_ROLE_SERVER)
998
- ? session->max_request_batch_items
999
- : session->max_response_batch_items;
1000
- if (hdr_out->item_count > max_batch)
1001
- return NIPC_UDS_ERR_LIMIT_EXCEEDED;
1002
-
1003
- /* Client-side: validate response message_id is in-flight */
1004
- if (session->role == NIPC_UDS_ROLE_CLIENT &&
1005
- hdr_out->kind == NIPC_KIND_RESPONSE) {
1006
- if (inflight_remove(session, hdr_out->message_id) < 0)
1007
- return NIPC_UDS_ERR_UNKNOWN_MSG_ID;
1008
- }
1009
-
1010
- size_t total_msg;
1011
- if (!header_payload_len(hdr_out->payload_len, &total_msg))
1012
- return NIPC_UDS_ERR_LIMIT_EXCEEDED;
1013
-
1014
- /* Non-chunked: entire message arrived in one packet */
1015
- if ((size_t)n >= total_msg) {
1016
- *payload_out = (const uint8_t *)buf + NIPC_HEADER_LEN;
1017
- *payload_len_out = hdr_out->payload_len;
1018
-
1019
- nipc_uds_error_t berr = validate_batch(hdr_out, *payload_out, *payload_len_out);
1020
- if (berr != NIPC_UDS_OK)
1021
- return berr;
1022
-
1023
- return NIPC_UDS_OK;
1024
- }
1025
-
1026
- /* Chunked: first packet has partial payload. The total message
1027
- * size is NIPC_HEADER_LEN + payload_len from the header. */
1028
- size_t first_payload_bytes = (size_t)n - NIPC_HEADER_LEN;
1029
-
1030
- /* We need a buffer for the full payload. Use the session recv_buf. */
1031
- nipc_uds_error_t err = ensure_recv_buf(session, hdr_out->payload_len);
1032
- if (err != NIPC_UDS_OK)
1033
- return err;
1034
-
1035
- /* Copy first chunk's payload into recv_buf */
1036
- memcpy(session->recv_buf, (uint8_t *)buf + NIPC_HEADER_LEN,
1037
- first_payload_bytes);
1038
-
1039
- size_t assembled = first_payload_bytes;
1040
- size_t chunk_payload_budget = session->packet_size - NIPC_HEADER_LEN;
1041
-
1042
- /* Calculate expected chunk count */
1043
- size_t remaining_after_first = hdr_out->payload_len - first_payload_bytes;
1044
- uint32_t expected_continuations = 0;
1045
- if (remaining_after_first > 0 && chunk_payload_budget > 0) {
1046
- expected_continuations = (uint32_t)((remaining_after_first +
1047
- chunk_payload_budget - 1)
1048
- / chunk_payload_budget);
1049
- }
1050
- uint32_t expected_chunk_count = 1 + expected_continuations;
1051
-
1052
- /* A temporary buffer for reading continuation packets */
1053
- size_t pkt_buf_size = session->packet_size;
1054
- uint8_t *pkt_buf = malloc(pkt_buf_size);
1055
- if (!pkt_buf)
1056
- return NIPC_UDS_ERR_ALLOC;
1057
-
1058
- for (uint32_t ci = 1; assembled < hdr_out->payload_len; ci++) {
1059
- ssize_t cn = raw_recv(session->fd, pkt_buf, pkt_buf_size);
1060
- if (cn <= 0) {
1061
- free(pkt_buf);
1062
- inflight_fail_all(session);
1063
- return NIPC_UDS_ERR_RECV;
1064
- }
1065
-
1066
- if ((size_t)cn < NIPC_HEADER_LEN) {
1067
- free(pkt_buf);
1068
- return NIPC_UDS_ERR_CHUNK;
1069
- }
1070
-
1071
- nipc_chunk_header_t chk;
1072
- perr = nipc_chunk_header_decode(pkt_buf, (size_t)cn, &chk);
1073
- if (perr != NIPC_OK) {
1074
- free(pkt_buf);
1075
- return NIPC_UDS_ERR_CHUNK;
1076
- }
1077
-
1078
- /* Validate chunk header */
1079
- if (chk.message_id != hdr_out->message_id ||
1080
- chk.chunk_index != ci ||
1081
- chk.chunk_count != expected_chunk_count ||
1082
- chk.total_message_len != (uint32_t)total_msg) {
1083
- free(pkt_buf);
1084
- return NIPC_UDS_ERR_CHUNK;
1085
- }
1086
-
1087
- size_t chunk_data = (size_t)cn - NIPC_HEADER_LEN;
1088
- if (chunk_data != chk.chunk_payload_len) {
1089
- free(pkt_buf);
1090
- return NIPC_UDS_ERR_CHUNK;
1091
- }
1092
-
1093
- if (assembled + chunk_data > hdr_out->payload_len) {
1094
- free(pkt_buf);
1095
- return NIPC_UDS_ERR_CHUNK;
1096
- }
1097
-
1098
- memcpy(session->recv_buf + assembled,
1099
- pkt_buf + NIPC_HEADER_LEN, chunk_data);
1100
- assembled += chunk_data;
1101
- }
1102
-
1103
- free(pkt_buf);
1104
-
1105
- *payload_out = session->recv_buf;
1106
- *payload_len_out = hdr_out->payload_len;
1107
-
1108
- nipc_uds_error_t berr = validate_batch(hdr_out, *payload_out, *payload_len_out);
1109
- if (berr != NIPC_UDS_OK)
1110
- return berr;
1111
-
1112
- return NIPC_UDS_OK;
76
+ return recv(fd, buf, buf_len, 0);
77
}
src/libnetdata/netipc/src/transport/posix/netipc_uds_handshake.c
new
+319
@@ -0,0 +1,319 @@
1
+#include "netipc_uds_internal.h"
2
+
3
+#include <string.h>
4
+
5
+static inline uint32_t min_u32(uint32_t a, uint32_t b)
6
+{
7
+ return a < b ? a : b;
8
+}
9
+
10
+static inline uint32_t apply_default(uint32_t val, uint32_t def)
11
+{
12
+ return val == 0 ? def : val;
13
+}
14
+
15
+static uint32_t highest_bit(uint32_t mask)
16
+{
17
+ if (mask == 0)
18
+ return 0;
19
+
20
+ uint32_t bit = 1u << 31;
21
+ while (!(mask & bit))
22
+ bit >>= 1;
23
+ return bit;
24
+}
25
+
26
+static bool header_version_incompatible(const void *buf, size_t buf_len,
27
+ uint16_t expected_code)
28
+{
29
+ if (buf_len < NIPC_HEADER_LEN)
30
+ return false;
31
+
32
+ nipc_header_t hdr;
33
+ memcpy(&hdr, buf, sizeof(hdr));
34
+ return hdr.magic == NIPC_MAGIC_MSG &&
35
+ hdr.version != NIPC_VERSION &&
36
+ hdr.header_len == NIPC_HEADER_LEN &&
37
+ hdr.kind == NIPC_KIND_CONTROL &&
38
+ hdr.code == expected_code;
39
+}
40
+
41
+static bool hello_layout_incompatible(const void *buf, size_t buf_len)
42
+{
43
+ if (buf_len < sizeof(uint16_t))
44
+ return false;
45
+
46
+ nipc_hello_t hello;
47
+ memset(&hello, 0, sizeof(hello));
48
+ memcpy(&hello, buf, sizeof(uint16_t));
49
+ return hello.layout_version != 1;
50
+}
51
+
52
+static bool hello_ack_layout_incompatible(const void *buf, size_t buf_len)
53
+{
54
+ if (buf_len < sizeof(uint16_t))
55
+ return false;
56
+
57
+ nipc_hello_ack_t ack;
58
+ memset(&ack, 0, sizeof(ack));
59
+ memcpy(&ack, buf, sizeof(uint16_t));
60
+ return ack.layout_version != 1;
61
+}
62
+
63
+static void encode_control_header(nipc_header_t *hdr, uint16_t code,
64
+ uint16_t status, uint32_t payload_len)
65
+{
66
+ *hdr = (nipc_header_t){
67
+ .magic = NIPC_MAGIC_MSG,
68
+ .version = NIPC_VERSION,
69
+ .header_len = NIPC_HEADER_LEN,
70
+ .kind = NIPC_KIND_CONTROL,
71
+ .code = code,
72
+ .transport_status = status,
73
+ .payload_len = payload_len,
74
+ .item_count = 1,
75
+ };
76
+}
77
+
78
+static void send_rejection_ack(int fd, uint16_t status)
79
+{
80
+ nipc_hello_ack_t ack = { .layout_version = 1 };
81
+ uint8_t ack_buf[48];
82
+ uint8_t pkt[80];
83
+ nipc_header_t ack_hdr;
84
+
85
+ encode_control_header(&ack_hdr, NIPC_CODE_HELLO_ACK, status,
86
+ sizeof(ack_buf));
87
+ nipc_hello_ack_encode(&ack, ack_buf, sizeof(ack_buf));
88
+ nipc_header_encode(&ack_hdr, pkt, sizeof(pkt));
89
+ memcpy(pkt + NIPC_HEADER_LEN, ack_buf, sizeof(ack_buf));
90
+ nipc_uds_raw_send(fd, pkt, NIPC_HEADER_LEN + sizeof(ack_buf));
91
+}
92
+
93
+static nipc_uds_error_t ack_status_to_error(uint16_t status)
94
+{
95
+ if (status == NIPC_STATUS_OK)
96
+ return NIPC_UDS_OK;
97
+ if (status == NIPC_STATUS_AUTH_FAILED)
98
+ return NIPC_UDS_ERR_AUTH_FAILED;
99
+ if (status == NIPC_STATUS_UNSUPPORTED)
100
+ return NIPC_UDS_ERR_NO_PROFILE;
101
+ if (status == NIPC_STATUS_INCOMPATIBLE)
102
+ return NIPC_UDS_ERR_INCOMPATIBLE;
103
+ if (status == NIPC_STATUS_LIMIT_EXCEEDED)
104
+ return NIPC_UDS_ERR_LIMIT_EXCEEDED;
105
+ return NIPC_UDS_ERR_HANDSHAKE;
106
+}
107
+
108
+static void fill_client_session(nipc_uds_session_t *session, int fd,
109
+ const nipc_hello_ack_t *ack)
110
+{
111
+ session->fd = fd;
112
+ session->role = NIPC_UDS_ROLE_CLIENT;
113
+ session->max_request_payload_bytes = ack->agreed_max_request_payload_bytes;
114
+ session->max_request_batch_items = ack->agreed_max_request_batch_items;
115
+ session->max_response_payload_bytes = ack->agreed_max_response_payload_bytes;
116
+ session->max_response_batch_items = ack->agreed_max_response_batch_items;
117
+ session->packet_size = ack->agreed_packet_size;
118
+ session->selected_profile = ack->selected_profile;
119
+ session->session_id = ack->session_id;
120
+ session->recv_buf = NULL;
121
+ session->recv_buf_size = 0;
122
+}
123
+
124
+static void fill_server_session(nipc_uds_session_t *session, int fd,
125
+ uint32_t selected,
126
+ const nipc_hello_ack_t *ack)
127
+{
128
+ session->fd = fd;
129
+ session->role = NIPC_UDS_ROLE_SERVER;
130
+ session->max_request_payload_bytes = ack->agreed_max_request_payload_bytes;
131
+ session->max_request_batch_items = ack->agreed_max_request_batch_items;
132
+ session->max_response_payload_bytes = ack->agreed_max_response_payload_bytes;
133
+ session->max_response_batch_items = ack->agreed_max_response_batch_items;
134
+ session->packet_size = ack->agreed_packet_size;
135
+ session->selected_profile = selected;
136
+ session->session_id = ack->session_id;
137
+ session->recv_buf = NULL;
138
+ session->recv_buf_size = 0;
139
+}
140
+
141
+nipc_uds_error_t nipc_uds_client_handshake(int fd,
142
+ const nipc_uds_client_config_t *cfg,
143
+ nipc_uds_session_t *session)
144
+{
145
+ uint8_t buf[128];
146
+ uint32_t pkt_size = cfg->packet_size;
147
+ if (pkt_size == 0)
148
+ pkt_size = nipc_uds_detect_packet_size(fd);
149
+
150
+ nipc_hello_t hello = {
151
+ .layout_version = 1,
152
+ .supported_profiles = cfg->supported_profiles ? cfg->supported_profiles : NIPC_PROFILE_BASELINE,
153
+ .preferred_profiles = cfg->preferred_profiles,
154
+ .max_request_payload_bytes = apply_default(cfg->max_request_payload_bytes, NIPC_MAX_PAYLOAD_DEFAULT),
155
+ .max_request_batch_items = apply_default(cfg->max_request_batch_items, UDS_DEFAULT_BATCH_ITEMS),
156
+ .max_response_payload_bytes = apply_default(cfg->max_response_payload_bytes, NIPC_MAX_PAYLOAD_DEFAULT),
157
+ .max_response_batch_items = apply_default(cfg->max_response_batch_items, UDS_DEFAULT_BATCH_ITEMS),
158
+ .auth_token = cfg->auth_token,
159
+ .packet_size = pkt_size,
160
+ };
161
+
162
+ uint8_t hello_buf[44];
163
+ nipc_hello_encode(&hello, hello_buf, sizeof(hello_buf));
164
+
165
+ nipc_header_t hdr;
166
+ encode_control_header(&hdr, NIPC_CODE_HELLO, NIPC_STATUS_OK,
167
+ sizeof(hello_buf));
168
+ nipc_header_encode(&hdr, buf, sizeof(buf));
169
+ memcpy(buf + NIPC_HEADER_LEN, hello_buf, sizeof(hello_buf));
170
+
171
+ nipc_uds_error_t err = nipc_uds_raw_send(
172
+ fd, buf, NIPC_HEADER_LEN + sizeof(hello_buf));
173
+ if (err != NIPC_UDS_OK)
174
+ return err;
175
+
176
+ ssize_t n = nipc_uds_raw_recv(fd, buf, sizeof(buf));
177
+ if (n <= 0)
178
+ return NIPC_UDS_ERR_RECV;
179
+
180
+ nipc_header_t ack_hdr;
181
+ nipc_error_t perr = nipc_header_decode(buf, (size_t)n, &ack_hdr);
182
+ if (perr == NIPC_ERR_BAD_VERSION)
183
+ return NIPC_UDS_ERR_INCOMPATIBLE;
184
+ if (perr != NIPC_OK)
185
+ return NIPC_UDS_ERR_PROTOCOL;
186
+
187
+ if (ack_hdr.kind != NIPC_KIND_CONTROL || ack_hdr.code != NIPC_CODE_HELLO_ACK)
188
+ return NIPC_UDS_ERR_PROTOCOL;
189
+
190
+ err = ack_status_to_error(ack_hdr.transport_status);
191
+ if (err != NIPC_UDS_OK)
192
+ return err;
193
+
194
+ nipc_hello_ack_t ack;
195
+ perr = nipc_hello_ack_decode(buf + NIPC_HEADER_LEN,
196
+ (size_t)n - NIPC_HEADER_LEN, &ack);
197
+ if (perr == NIPC_ERR_BAD_LAYOUT &&
198
+ hello_ack_layout_incompatible(buf + NIPC_HEADER_LEN,
199
+ (size_t)n - NIPC_HEADER_LEN))
200
+ return NIPC_UDS_ERR_INCOMPATIBLE;
201
+ if (perr != NIPC_OK)
202
+ return NIPC_UDS_ERR_PROTOCOL;
203
+
204
+ fill_client_session(session, fd, &ack);
205
+ if (session->packet_size <= NIPC_HEADER_LEN)
206
+ return NIPC_UDS_ERR_PROTOCOL;
207
+
208
+ return NIPC_UDS_OK;
209
+}
210
+
211
+nipc_uds_error_t nipc_uds_server_handshake(int fd,
212
+ const nipc_uds_server_config_t *cfg,
213
+ uint64_t session_id,
214
+ nipc_uds_session_t *session)
215
+{
216
+ uint8_t buf[128];
217
+ uint32_t server_pkt_size = cfg->packet_size;
218
+ if (server_pkt_size == 0)
219
+ server_pkt_size = nipc_uds_detect_packet_size(fd);
220
+
221
+ uint32_t s_resp_pay = apply_default(cfg->max_response_payload_bytes,
222
+ NIPC_MAX_PAYLOAD_DEFAULT);
223
+ uint32_t s_profiles = cfg->supported_profiles ? cfg->supported_profiles : NIPC_PROFILE_BASELINE;
224
+ uint32_t s_preferred = cfg->preferred_profiles;
225
+
226
+ ssize_t n = nipc_uds_raw_recv(fd, buf, sizeof(buf));
227
+ if (n <= 0)
228
+ return NIPC_UDS_ERR_RECV;
229
+
230
+ nipc_header_t hdr;
231
+ nipc_error_t perr = nipc_header_decode(buf, (size_t)n, &hdr);
232
+ if (perr == NIPC_ERR_BAD_VERSION &&
233
+ header_version_incompatible(buf, (size_t)n, NIPC_CODE_HELLO)) {
234
+ send_rejection_ack(fd, NIPC_STATUS_INCOMPATIBLE);
235
+ return NIPC_UDS_ERR_INCOMPATIBLE;
236
+ }
237
+ if (perr != NIPC_OK)
238
+ return NIPC_UDS_ERR_PROTOCOL;
239
+
240
+ if (hdr.kind != NIPC_KIND_CONTROL || hdr.code != NIPC_CODE_HELLO)
241
+ return NIPC_UDS_ERR_PROTOCOL;
242
+
243
+ nipc_hello_t hello;
244
+ perr = nipc_hello_decode(buf + NIPC_HEADER_LEN,
245
+ (size_t)n - NIPC_HEADER_LEN, &hello);
246
+ if (perr == NIPC_ERR_BAD_LAYOUT &&
247
+ hello_layout_incompatible(buf + NIPC_HEADER_LEN,
248
+ (size_t)n - NIPC_HEADER_LEN)) {
249
+ send_rejection_ack(fd, NIPC_STATUS_INCOMPATIBLE);
250
+ return NIPC_UDS_ERR_INCOMPATIBLE;
251
+ }
252
+ if (perr != NIPC_OK)
253
+ return NIPC_UDS_ERR_PROTOCOL;
254
+
255
+ uint32_t intersection = hello.supported_profiles & s_profiles;
256
+ if (intersection == 0) {
257
+ send_rejection_ack(fd, NIPC_STATUS_UNSUPPORTED);
258
+ return NIPC_UDS_ERR_NO_PROFILE;
259
+ }
260
+
261
+ if (hello.auth_token != cfg->auth_token) {
262
+ send_rejection_ack(fd, NIPC_STATUS_AUTH_FAILED);
263
+ return NIPC_UDS_ERR_AUTH_FAILED;
264
+ }
265
+
266
+ uint32_t preferred_intersection = intersection &
267
+ hello.preferred_profiles & s_preferred;
268
+ uint32_t selected = preferred_intersection != 0
269
+ ? highest_bit(preferred_intersection)
270
+ : highest_bit(intersection);
271
+
272
+ if (hello.max_request_payload_bytes > NIPC_MAX_PAYLOAD_CAP) {
273
+ send_rejection_ack(fd, NIPC_STATUS_LIMIT_EXCEEDED);
274
+ return NIPC_UDS_ERR_LIMIT_EXCEEDED;
275
+ }
276
+
277
+ uint32_t agreed_req_pay = hello.max_request_payload_bytes;
278
+ uint32_t agreed_req_bat = hello.max_request_batch_items;
279
+ uint32_t agreed_resp_pay = s_resp_pay;
280
+ uint32_t agreed_resp_bat = agreed_req_bat;
281
+ uint32_t agreed_pkt = min_u32(hello.packet_size, server_pkt_size);
282
+
283
+ if (agreed_pkt <= NIPC_HEADER_LEN) {
284
+ send_rejection_ack(fd, NIPC_STATUS_INCOMPATIBLE);
285
+ return NIPC_UDS_ERR_INCOMPATIBLE;
286
+ }
287
+
288
+ nipc_hello_ack_t ack = {
289
+ .layout_version = 1,
290
+ .server_supported_profiles = s_profiles,
291
+ .intersection_profiles = intersection,
292
+ .selected_profile = selected,
293
+ .agreed_max_request_payload_bytes = agreed_req_pay,
294
+ .agreed_max_request_batch_items = agreed_req_bat,
295
+ .agreed_max_response_payload_bytes = agreed_resp_pay,
296
+ .agreed_max_response_batch_items = agreed_resp_bat,
297
+ .agreed_packet_size = agreed_pkt,
298
+ .session_id = session_id,
299
+ };
300
+
301
+ uint8_t ack_buf[48];
302
+ nipc_hello_ack_encode(&ack, ack_buf, sizeof(ack_buf));
303
+
304
+ nipc_header_t ack_hdr;
305
+ encode_control_header(&ack_hdr, NIPC_CODE_HELLO_ACK, NIPC_STATUS_OK,
306
+ sizeof(ack_buf));
307
+
308
+ uint8_t pkt[80];
309
+ nipc_header_encode(&ack_hdr, pkt, sizeof(pkt));
310
+ memcpy(pkt + NIPC_HEADER_LEN, ack_buf, sizeof(ack_buf));
311
+
312
+ nipc_uds_error_t send_ack_err = nipc_uds_raw_send(
313
+ fd, pkt, NIPC_HEADER_LEN + sizeof(ack_buf));
314
+ if (send_ack_err != NIPC_UDS_OK)
315
+ return send_ack_err;
316
+
317
+ fill_server_session(session, fd, selected, &ack);
318
+ return NIPC_UDS_OK;
319
+}
src/libnetdata/netipc/src/transport/posix/netipc_uds_inflight.c
new
+44
@@ -0,0 +1,44 @@
1
+#include "netipc_uds_internal.h"
2
+
3
+#include <stdlib.h>
4
+
5
+int nipc_uds_inflight_add(nipc_uds_session_t *s, uint64_t id)
6
+{
7
+ for (uint32_t i = 0; i < s->inflight_count; i++) {
8
+ if (s->inflight_ids[i] == id)
9
+ return -1;
10
+ }
11
+
12
+ if (s->inflight_count >= s->inflight_capacity) {
13
+ uint32_t new_cap = s->inflight_capacity ? s->inflight_capacity * 2 : 16;
14
+ uint64_t *new_ids = realloc(s->inflight_ids,
15
+ (size_t)new_cap * sizeof(uint64_t));
16
+ if (!new_ids)
17
+ return -2;
18
+ s->inflight_ids = new_ids;
19
+ s->inflight_capacity = new_cap;
20
+ }
21
+
22
+ s->inflight_ids[s->inflight_count++] = id;
23
+ return 0;
24
+}
25
+
26
+int nipc_uds_inflight_remove(nipc_uds_session_t *s, uint64_t id)
27
+{
28
+ for (uint32_t i = 0; i < s->inflight_count; i++) {
29
+ if (s->inflight_ids[i] == id) {
30
+ s->inflight_ids[i] = s->inflight_ids[s->inflight_count - 1];
31
+ s->inflight_count--;
32
+ return 0;
33
+ }
34
+ }
35
+ return -1;
36
+}
37
+
38
+void nipc_uds_inflight_fail_all(nipc_uds_session_t *s)
39
+{
40
+ if (!s || s->role != NIPC_UDS_ROLE_CLIENT)
41
+ return;
42
+
43
+ s->inflight_count = 0;
44
+}
src/libnetdata/netipc/src/transport/posix/netipc_uds_internal.h
new
+46
@@ -0,0 +1,46 @@
1
+#ifndef NETIPC_UDS_INTERNAL_H
2
+#define NETIPC_UDS_INTERNAL_H
3
+
4
+#include "netipc/netipc_protocol.h"
5
+#include "netipc/netipc_uds.h"
6
+
7
+#include <stdbool.h>
8
+#include <stddef.h>
9
+#include <stdint.h>
10
+#include <sys/types.h>
11
+
12
+#define UDS_DEFAULT_BACKLOG 16
13
+#define UDS_DEFAULT_BATCH_ITEMS 1
14
+
15
+bool nipc_uds_header_payload_len(size_t payload_len, size_t *msg_len_out);
16
+uint32_t nipc_uds_detect_packet_size(int fd);
17
+
18
+nipc_uds_error_t nipc_uds_raw_send(int fd, const void *data, size_t len);
19
+nipc_uds_error_t nipc_uds_raw_send_iov(int fd, const void *hdr, size_t hdr_len,
20
+ const void *payload, size_t payload_len);
21
+ssize_t nipc_uds_raw_recv(int fd, void *buf, size_t buf_len);
22
+
23
+int nipc_uds_build_socket_name(char *dst, size_t dst_len,
24
+ const char *service_name);
25
+int nipc_uds_build_socket_path(char *dst, size_t dst_len,
26
+ const char *run_dir,
27
+ const char *service_name);
28
+bool nipc_uds_run_dir_allows_stale_unlink(const char *run_dir);
29
+int nipc_uds_check_and_recover_stale(const char *run_dir,
30
+ const char *socket_name,
31
+ const char *path,
32
+ bool allow_stale_unlink);
33
+
34
+nipc_uds_error_t nipc_uds_client_handshake(int fd,
35
+ const nipc_uds_client_config_t *cfg,
36
+ nipc_uds_session_t *session);
37
+nipc_uds_error_t nipc_uds_server_handshake(int fd,
38
+ const nipc_uds_server_config_t *cfg,
39
+ uint64_t session_id,
40
+ nipc_uds_session_t *session);
41
+
42
+int nipc_uds_inflight_add(nipc_uds_session_t *s, uint64_t id);
43
+int nipc_uds_inflight_remove(nipc_uds_session_t *s, uint64_t id);
44
+void nipc_uds_inflight_fail_all(nipc_uds_session_t *s);
45
+
46
+#endif /* NETIPC_UDS_INTERNAL_H */
src/libnetdata/netipc/src/transport/posix/netipc_uds_lifecycle.c
new
+329
@@ -0,0 +1,329 @@
1
+#include "netipc_uds_internal.h"
2
+
3
+#include <dirent.h>
4
+#include <errno.h>
5
+#include <fcntl.h>
6
+#include <stdio.h>
7
+#include <stdlib.h>
8
+#include <string.h>
9
+#include <unistd.h>
10
+
11
+#include <sys/socket.h>
12
+#include <sys/stat.h>
13
+#include <sys/un.h>
14
+
15
+static int copy_cstr_checked(char *dst, size_t dst_size, const char *src)
16
+{
17
+ if (!dst || !src || dst_size == 0)
18
+ return -1;
19
+
20
+ size_t len = 0;
21
+ while (len < dst_size && src[len] != '\0')
22
+ len++;
23
+ if (len == dst_size)
24
+ return -1;
25
+
26
+ memcpy(dst, src, len + 1);
27
+ return 0;
28
+}
29
+
30
+static int fill_sockaddr_path(struct sockaddr_un *addr, const char *path)
31
+{
32
+ memset(addr, 0, sizeof(*addr));
33
+ addr->sun_family = AF_UNIX;
34
+ return copy_cstr_checked(addr->sun_path, sizeof(addr->sun_path), path);
35
+}
36
+
37
+static int validate_service_name(const char *name)
38
+{
39
+ if (!name || name[0] == '\0')
40
+ return -1;
41
+
42
+ if (name[0] == '.' && (name[1] == '\0' ||
43
+ (name[1] == '.' && name[2] == '\0')))
44
+ return -1;
45
+
46
+ for (const char *p = name; *p; p++) {
47
+ char c = *p;
48
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
49
+ (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-')
50
+ continue;
51
+ return -1;
52
+ }
53
+ return 0;
54
+}
55
+
56
+int nipc_uds_build_socket_name(char *dst, size_t dst_len,
57
+ const char *service_name)
58
+{
59
+ if (validate_service_name(service_name) < 0)
60
+ return -2;
61
+
62
+ int n = snprintf(dst, dst_len, "%s.sock", service_name);
63
+ if (n < 0 || (size_t)n >= dst_len)
64
+ return -1;
65
+ return 0;
66
+}
67
+
68
+int nipc_uds_build_socket_path(char *dst, size_t dst_len,
69
+ const char *run_dir,
70
+ const char *service_name)
71
+{
72
+ char socket_name[sizeof(((struct sockaddr_un *)0)->sun_path)];
73
+ int name_rc = nipc_uds_build_socket_name(socket_name, sizeof(socket_name),
74
+ service_name);
75
+ if (name_rc < 0)
76
+ return name_rc;
77
+
78
+ int n = snprintf(dst, dst_len, "%s/%s", run_dir, socket_name);
79
+ if (n < 0 || (size_t)n >= dst_len)
80
+ return -1;
81
+ return 0;
82
+}
83
+
84
+bool nipc_uds_run_dir_allows_stale_unlink(const char *run_dir)
85
+{
86
+ struct stat st;
87
+ if (stat(run_dir, &st) != 0)
88
+ return false;
89
+ if (!S_ISDIR(st.st_mode))
90
+ return false;
91
+ if (st.st_uid != geteuid())
92
+ return false;
93
+ return (st.st_mode & (S_IWGRP | S_IWOTH)) == 0;
94
+}
95
+
96
+static int unlink_stale_socket_path(const char *run_dir,
97
+ const char *socket_name,
98
+ bool allow_stale_unlink)
99
+{
100
+ if (!allow_stale_unlink)
101
+ return -1;
102
+
103
+ DIR *dir = opendir(run_dir);
104
+ if (!dir)
105
+ return -1;
106
+
107
+ int dir_fd = dirfd(dir);
108
+ if (dir_fd < 0) {
109
+ closedir(dir);
110
+ return -1;
111
+ }
112
+
113
+ struct stat st;
114
+ if (fstatat(dir_fd, socket_name, &st, AT_SYMLINK_NOFOLLOW) != 0) {
115
+ int ret = (errno == ENOENT) ? 0 : -1;
116
+ closedir(dir);
117
+ return ret;
118
+ }
119
+
120
+ if (!S_ISSOCK(st.st_mode)) {
121
+ closedir(dir);
122
+ return -1;
123
+ }
124
+
125
+ int ret = -1;
126
+ if (unlinkat(dir_fd, socket_name, 0) == 0 || errno == ENOENT)
127
+ ret = 0;
128
+
129
+ closedir(dir);
130
+ return ret;
131
+}
132
+
133
+int nipc_uds_check_and_recover_stale(const char *run_dir,
134
+ const char *socket_name,
135
+ const char *path,
136
+ bool allow_stale_unlink)
137
+{
138
+ int probe = socket(AF_UNIX, SOCK_SEQPACKET, 0);
139
+ if (probe < 0)
140
+ return -1;
141
+
142
+ struct sockaddr_un addr;
143
+ if (fill_sockaddr_path(&addr, path) != 0) {
144
+ close(probe);
145
+ return -1;
146
+ }
147
+
148
+ int ret;
149
+ if (connect(probe, (struct sockaddr *)&addr, sizeof(addr)) == 0) {
150
+ close(probe);
151
+ ret = 1;
152
+ } else {
153
+ int saved_errno = errno;
154
+ close(probe);
155
+ if (saved_errno == ENOENT) {
156
+ ret = -1;
157
+ } else if (saved_errno == ECONNREFUSED) {
158
+ ret = (unlink_stale_socket_path(run_dir, socket_name,
159
+ allow_stale_unlink) == 0) ? 0 : 1;
160
+ } else {
161
+ ret = 1;
162
+ }
163
+ }
164
+ return ret;
165
+}
166
+
167
+nipc_uds_error_t nipc_uds_listen(const char *run_dir,
168
+ const char *service_name,
169
+ const nipc_uds_server_config_t *config,
170
+ nipc_uds_listener_t *out)
171
+{
172
+ memset(out, 0, sizeof(*out));
173
+ out->fd = -1;
174
+
175
+ char path[sizeof(((struct sockaddr_un *)0)->sun_path)];
176
+ int path_rc = nipc_uds_build_socket_path(path, sizeof(path), run_dir,
177
+ service_name);
178
+ if (path_rc == -2)
179
+ return NIPC_UDS_ERR_BAD_PARAM;
180
+ if (path_rc < 0)
181
+ return NIPC_UDS_ERR_PATH_TOO_LONG;
182
+
183
+ char socket_name[sizeof(((struct sockaddr_un *)0)->sun_path)];
184
+ int name_rc = nipc_uds_build_socket_name(socket_name, sizeof(socket_name),
185
+ service_name);
186
+ if (name_rc == -2)
187
+ return NIPC_UDS_ERR_BAD_PARAM;
188
+ if (name_rc < 0)
189
+ return NIPC_UDS_ERR_PATH_TOO_LONG;
190
+
191
+ bool allow_stale_unlink = nipc_uds_run_dir_allows_stale_unlink(run_dir);
192
+ int stale = nipc_uds_check_and_recover_stale(run_dir, socket_name, path,
193
+ allow_stale_unlink);
194
+ if (stale == 1)
195
+ return NIPC_UDS_ERR_ADDR_IN_USE;
196
+
197
+ int fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
198
+ if (fd < 0)
199
+ return NIPC_UDS_ERR_SOCKET;
200
+
201
+ struct sockaddr_un addr;
202
+ if (fill_sockaddr_path(&addr, path) != 0) {
203
+ close(fd);
204
+ return NIPC_UDS_ERR_PATH_TOO_LONG;
205
+ }
206
+
207
+ if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
208
+ close(fd);
209
+ return NIPC_UDS_ERR_SOCKET;
210
+ }
211
+
212
+ int backlog = config->backlog > 0 ? config->backlog : UDS_DEFAULT_BACKLOG;
213
+ if (listen(fd, backlog) < 0) {
214
+ close(fd);
215
+ unlink(path);
216
+ return NIPC_UDS_ERR_SOCKET;
217
+ }
218
+
219
+ out->fd = fd;
220
+ out->config = *config;
221
+ if (copy_cstr_checked(out->path, sizeof(out->path), path) != 0) {
222
+ close(fd);
223
+ unlink(path);
224
+ memset(out, 0, sizeof(*out));
225
+ out->fd = -1;
226
+ return NIPC_UDS_ERR_PATH_TOO_LONG;
227
+ }
228
+
229
+ return NIPC_UDS_OK;
230
+}
231
+
232
+nipc_uds_error_t nipc_uds_accept(nipc_uds_listener_t *listener,
233
+ uint64_t session_id,
234
+ nipc_uds_session_t *out)
235
+{
236
+ memset(out, 0, sizeof(*out));
237
+ out->fd = -1;
238
+
239
+ int client_fd = accept(listener->fd, NULL, NULL);
240
+ if (client_fd < 0)
241
+ return NIPC_UDS_ERR_ACCEPT;
242
+
243
+ nipc_uds_error_t err = nipc_uds_server_handshake(
244
+ client_fd, &listener->config, session_id, out);
245
+ if (err != NIPC_UDS_OK) {
246
+ close(client_fd);
247
+ out->fd = -1;
248
+ return err;
249
+ }
250
+
251
+ return NIPC_UDS_OK;
252
+}
253
+
254
+nipc_uds_error_t nipc_uds_connect(const char *run_dir,
255
+ const char *service_name,
256
+ const nipc_uds_client_config_t *config,
257
+ nipc_uds_session_t *out)
258
+{
259
+ memset(out, 0, sizeof(*out));
260
+ out->fd = -1;
261
+
262
+ char path[sizeof(((struct sockaddr_un *)0)->sun_path)];
263
+ int path_rc = nipc_uds_build_socket_path(path, sizeof(path), run_dir,
264
+ service_name);
265
+ if (path_rc == -2)
266
+ return NIPC_UDS_ERR_BAD_PARAM;
267
+ if (path_rc < 0)
268
+ return NIPC_UDS_ERR_PATH_TOO_LONG;
269
+
270
+ int fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
271
+ if (fd < 0)
272
+ return NIPC_UDS_ERR_SOCKET;
273
+
274
+ struct sockaddr_un addr;
275
+ if (fill_sockaddr_path(&addr, path) != 0) {
276
+ close(fd);
277
+ return NIPC_UDS_ERR_PATH_TOO_LONG;
278
+ }
279
+
280
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
281
+ close(fd);
282
+ return NIPC_UDS_ERR_CONNECT;
283
+ }
284
+
285
+ nipc_uds_error_t err = nipc_uds_client_handshake(fd, config, out);
286
+ if (err != NIPC_UDS_OK) {
287
+ close(fd);
288
+ out->fd = -1;
289
+ return err;
290
+ }
291
+
292
+ return NIPC_UDS_OK;
293
+}
294
+
295
+void nipc_uds_close_session(nipc_uds_session_t *session)
296
+{
297
+ if (!session)
298
+ return;
299
+
300
+ if (session->fd >= 0) {
301
+ close(session->fd);
302
+ session->fd = -1;
303
+ }
304
+
305
+ free(session->recv_buf);
306
+ session->recv_buf = NULL;
307
+ session->recv_buf_size = 0;
308
+
309
+ free(session->inflight_ids);
310
+ session->inflight_ids = NULL;
311
+ session->inflight_count = 0;
312
+ session->inflight_capacity = 0;
313
+}
314
+
315
+void nipc_uds_close_listener(nipc_uds_listener_t *listener)
316
+{
317
+ if (!listener)
318
+ return;
319
+
320
+ if (listener->fd >= 0) {
321
+ close(listener->fd);
322
+ listener->fd = -1;
323
+ }
324
+
325
+ if (listener->path[0]) {
326
+ unlink(listener->path);
327
+ listener->path[0] = '\0';
328
+ }
329
+}
src/libnetdata/netipc/src/transport/posix/netipc_uds_receive.c
new
+257
@@ -0,0 +1,257 @@
1
+#include "netipc_uds_internal.h"
2
+
3
+#include <stdlib.h>
4
+#include <string.h>
5
+
6
+static nipc_uds_error_t ensure_recv_buf(nipc_uds_session_t *session,
7
+ size_t needed)
8
+{
9
+ if (session->recv_buf_size >= needed)
10
+ return NIPC_UDS_OK;
11
+
12
+ uint8_t *p = realloc(session->recv_buf, needed);
13
+ if (!p)
14
+ return NIPC_UDS_ERR_ALLOC;
15
+
16
+ session->recv_buf = p;
17
+ session->recv_buf_size = needed;
18
+ return NIPC_UDS_OK;
19
+}
20
+
21
+static nipc_uds_error_t validate_batch(const nipc_header_t *hdr,
22
+ const void *payload,
23
+ size_t payload_len)
24
+{
25
+ if (!(hdr->flags & NIPC_FLAG_BATCH) || hdr->item_count <= 1)
26
+ return NIPC_UDS_OK;
27
+
28
+ uint32_t dir_bytes = hdr->item_count * 8;
29
+ uint32_t dir_aligned = (uint32_t)nipc_align8(dir_bytes);
30
+ if (payload_len < dir_aligned)
31
+ return NIPC_UDS_ERR_PROTOCOL;
32
+
33
+ uint32_t packed_area_len = (uint32_t)(payload_len - dir_aligned);
34
+ nipc_error_t perr = nipc_batch_dir_validate(payload, dir_bytes,
35
+ hdr->item_count,
36
+ packed_area_len);
37
+ return (perr == NIPC_OK) ? NIPC_UDS_OK : NIPC_UDS_ERR_PROTOCOL;
38
+}
39
+
40
+static nipc_uds_error_t validate_inbound_limits(
41
+ const nipc_uds_session_t *session,
42
+ const nipc_header_t *hdr)
43
+{
44
+ uint32_t max_payload = (session->role == NIPC_UDS_ROLE_SERVER)
45
+ ? session->max_request_payload_bytes
46
+ : session->max_response_payload_bytes;
47
+ if (hdr->payload_len > max_payload)
48
+ return NIPC_UDS_ERR_LIMIT_EXCEEDED;
49
+
50
+ uint32_t max_batch = (session->role == NIPC_UDS_ROLE_SERVER)
51
+ ? session->max_request_batch_items
52
+ : session->max_response_batch_items;
53
+ if (hdr->item_count > max_batch)
54
+ return NIPC_UDS_ERR_LIMIT_EXCEEDED;
55
+
56
+ return NIPC_UDS_OK;
57
+}
58
+
59
+static nipc_uds_error_t track_inbound_response(nipc_uds_session_t *session,
60
+ const nipc_header_t *hdr)
61
+{
62
+ if (session->role != NIPC_UDS_ROLE_CLIENT ||
63
+ hdr->kind != NIPC_KIND_RESPONSE)
64
+ return NIPC_UDS_OK;
65
+
66
+ if (nipc_uds_inflight_remove(session, hdr->message_id) < 0)
67
+ return NIPC_UDS_ERR_UNKNOWN_MSG_ID;
68
+ return NIPC_UDS_OK;
69
+}
70
+
71
+static nipc_uds_error_t receive_first_packet(nipc_uds_session_t *session,
72
+ void *buf,
73
+ size_t buf_size,
74
+ nipc_header_t *hdr_out,
75
+ ssize_t *received_out)
76
+{
77
+ ssize_t n = nipc_uds_raw_recv(session->fd, buf, buf_size);
78
+ if (n <= 0) {
79
+ nipc_uds_inflight_fail_all(session);
80
+ return NIPC_UDS_ERR_RECV;
81
+ }
82
+
83
+ if ((size_t)n < NIPC_HEADER_LEN)
84
+ return NIPC_UDS_ERR_PROTOCOL;
85
+
86
+ nipc_error_t perr = nipc_header_decode(buf, (size_t)n, hdr_out);
87
+ if (perr != NIPC_OK)
88
+ return NIPC_UDS_ERR_PROTOCOL;
89
+
90
+ *received_out = n;
91
+ return NIPC_UDS_OK;
92
+}
93
+
94
+static nipc_uds_error_t return_complete_packet(void *buf,
95
+ const nipc_header_t *hdr,
96
+ const void **payload_out,
97
+ size_t *payload_len_out)
98
+{
99
+ *payload_out = (const uint8_t *)buf + NIPC_HEADER_LEN;
100
+ *payload_len_out = hdr->payload_len;
101
+ return validate_batch(hdr, *payload_out, *payload_len_out);
102
+}
103
+
104
+static uint32_t expected_chunk_count(size_t payload_len,
105
+ size_t first_payload_bytes,
106
+ size_t chunk_payload_budget)
107
+{
108
+ size_t remaining_after_first = payload_len - first_payload_bytes;
109
+ uint32_t expected_continuations = 0;
110
+ if (remaining_after_first > 0 && chunk_payload_budget > 0) {
111
+ expected_continuations = (uint32_t)(1 + ((remaining_after_first - 1)
112
+ / chunk_payload_budget));
113
+ }
114
+ return 1 + expected_continuations;
115
+}
116
+
117
+static nipc_uds_error_t validate_chunk_header(const nipc_chunk_header_t *chk,
118
+ const nipc_header_t *hdr,
119
+ uint32_t chunk_index,
120
+ uint32_t chunk_count,
121
+ size_t total_msg)
122
+{
123
+ if (chk->message_id != hdr->message_id ||
124
+ chk->chunk_index != chunk_index ||
125
+ chk->chunk_count != chunk_count ||
126
+ chk->total_message_len != (uint32_t)total_msg)
127
+ return NIPC_UDS_ERR_CHUNK;
128
+ return NIPC_UDS_OK;
129
+}
130
+
131
+static nipc_uds_error_t receive_one_chunk(nipc_uds_session_t *session,
132
+ uint8_t *pkt_buf,
133
+ size_t pkt_buf_size,
134
+ const nipc_header_t *hdr,
135
+ uint32_t chunk_index,
136
+ uint32_t chunk_count,
137
+ size_t total_msg,
138
+ size_t *assembled)
139
+{
140
+ ssize_t cn = nipc_uds_raw_recv(session->fd, pkt_buf, pkt_buf_size);
141
+ if (cn <= 0) {
142
+ nipc_uds_inflight_fail_all(session);
143
+ return NIPC_UDS_ERR_RECV;
144
+ }
145
+
146
+ if ((size_t)cn < NIPC_HEADER_LEN)
147
+ return NIPC_UDS_ERR_CHUNK;
148
+ if ((size_t)cn > pkt_buf_size)
149
+ return NIPC_UDS_ERR_CHUNK;
150
+
151
+ nipc_chunk_header_t chk;
152
+ nipc_error_t perr = nipc_chunk_header_decode(pkt_buf, (size_t)cn, &chk);
153
+ if (perr != NIPC_OK)
154
+ return NIPC_UDS_ERR_CHUNK;
155
+
156
+ nipc_uds_error_t err = validate_chunk_header(
157
+ &chk, hdr, chunk_index, chunk_count, total_msg);
158
+ if (err != NIPC_UDS_OK)
159
+ return err;
160
+
161
+ size_t chunk_data = (size_t)cn - NIPC_HEADER_LEN;
162
+ if (chunk_data != chk.chunk_payload_len)
163
+ return NIPC_UDS_ERR_CHUNK;
164
+
165
+ if (*assembled > hdr->payload_len ||
166
+ chunk_data > hdr->payload_len - *assembled)
167
+ return NIPC_UDS_ERR_CHUNK;
168
+
169
+ memcpy(&session->recv_buf[*assembled], &pkt_buf[NIPC_HEADER_LEN],
170
+ chunk_data);
171
+ *assembled += chunk_data;
172
+ return NIPC_UDS_OK;
173
+}
174
+
175
+static nipc_uds_error_t receive_chunked_payload(nipc_uds_session_t *session,
176
+ void *buf,
177
+ ssize_t first_packet_len,
178
+ const nipc_header_t *hdr,
179
+ size_t total_msg,
180
+ const void **payload_out,
181
+ size_t *payload_len_out)
182
+{
183
+ size_t first_payload_bytes = (size_t)first_packet_len - NIPC_HEADER_LEN;
184
+ if (first_payload_bytes > hdr->payload_len)
185
+ return NIPC_UDS_ERR_CHUNK;
186
+
187
+ nipc_uds_error_t err = ensure_recv_buf(session, hdr->payload_len);
188
+ if (err != NIPC_UDS_OK)
189
+ return err;
190
+
191
+ memcpy(session->recv_buf, (uint8_t *)buf + NIPC_HEADER_LEN,
192
+ first_payload_bytes);
193
+
194
+ size_t assembled = first_payload_bytes;
195
+ size_t chunk_payload_budget = session->packet_size - NIPC_HEADER_LEN;
196
+ uint32_t chunk_count = expected_chunk_count(
197
+ hdr->payload_len, first_payload_bytes, chunk_payload_budget);
198
+
199
+ size_t pkt_buf_size = session->packet_size;
200
+ uint8_t *pkt_buf = malloc(pkt_buf_size);
201
+ if (!pkt_buf)
202
+ return NIPC_UDS_ERR_ALLOC;
203
+
204
+ for (uint32_t ci = 1; assembled < hdr->payload_len; ci++) {
205
+ err = receive_one_chunk(session, pkt_buf, pkt_buf_size, hdr, ci,
206
+ chunk_count, total_msg, &assembled);
207
+ if (err != NIPC_UDS_OK) {
208
+ free(pkt_buf);
209
+ return err;
210
+ }
211
+ }
212
+
213
+ free(pkt_buf);
214
+
215
+ *payload_out = session->recv_buf;
216
+ *payload_len_out = hdr->payload_len;
217
+ return validate_batch(hdr, *payload_out, *payload_len_out);
218
+}
219
+
220
+nipc_uds_error_t nipc_uds_receive(nipc_uds_session_t *session,
221
+ void *buf, size_t buf_size,
222
+ nipc_header_t *hdr_out,
223
+ const void **payload_out,
224
+ size_t *payload_len_out)
225
+{
226
+ if (!session || session->fd < 0)
227
+ return NIPC_UDS_ERR_BAD_PARAM;
228
+
229
+ ssize_t n;
230
+ nipc_uds_error_t err = receive_first_packet(session, buf, buf_size,
231
+ hdr_out, &n);
232
+ if (err != NIPC_UDS_OK)
233
+ return err;
234
+
235
+ err = validate_inbound_limits(session, hdr_out);
236
+ if (err != NIPC_UDS_OK)
237
+ return err;
238
+
239
+ err = track_inbound_response(session, hdr_out);
240
+ if (err != NIPC_UDS_OK)
241
+ return err;
242
+
243
+ size_t total_msg;
244
+ if (!nipc_uds_header_payload_len(hdr_out->payload_len, &total_msg))
245
+ return NIPC_UDS_ERR_LIMIT_EXCEEDED;
246
+
247
+ if ((size_t)n > total_msg)
248
+ return NIPC_UDS_ERR_PROTOCOL;
249
+
250
+ if ((size_t)n == total_msg) {
251
+ return return_complete_packet(buf, hdr_out, payload_out,
252
+ payload_len_out);
253
+ }
254
+
255
+ return receive_chunked_payload(session, buf, n, hdr_out, total_msg,
256
+ payload_out, payload_len_out);
257
+}
src/libnetdata/netipc/src/transport/posix/netipc_uds_send.c
new
+244
@@ -0,0 +1,244 @@
1
+#include "netipc_uds_internal.h"
2
+
3
+#include <assert.h>
4
+#include <string.h>
5
+
6
+static size_t min_of_size_t(size_t a, size_t b)
7
+{
8
+ return a < b ? a : b;
9
+}
10
+
11
+static bool tracks_client_request(const nipc_uds_session_t *session,
12
+ const nipc_header_t *hdr)
13
+{
14
+ return session->role == NIPC_UDS_ROLE_CLIENT &&
15
+ hdr->kind == NIPC_KIND_REQUEST;
16
+}
17
+
18
+static void update_inflight_after_send(nipc_uds_session_t *session,
19
+ const nipc_header_t *hdr,
20
+ bool tracked,
21
+ nipc_uds_error_t err)
22
+{
23
+ if (!tracked)
24
+ return;
25
+
26
+ if (err == NIPC_UDS_OK)
27
+ return;
28
+
29
+ if (err == NIPC_UDS_ERR_SEND)
30
+ nipc_uds_inflight_fail_all(session);
31
+ else
32
+ nipc_uds_inflight_remove(session, hdr->message_id);
33
+}
34
+
35
+static nipc_uds_error_t track_outbound_request(nipc_uds_session_t *session,
36
+ const nipc_header_t *hdr,
37
+ bool tracked)
38
+{
39
+ if (!tracked)
40
+ return NIPC_UDS_OK;
41
+
42
+ int rc = nipc_uds_inflight_add(session, hdr->message_id);
43
+ if (rc == -1)
44
+ return NIPC_UDS_ERR_DUPLICATE_MSG_ID;
45
+ if (rc == -2)
46
+ return NIPC_UDS_ERR_LIMIT_EXCEEDED;
47
+ return NIPC_UDS_OK;
48
+}
49
+
50
+static void outbound_limits(const nipc_uds_session_t *session,
51
+ const nipc_header_t *hdr,
52
+ uint32_t *max_payload,
53
+ uint32_t *max_batch)
54
+{
55
+ *max_payload = 0;
56
+ *max_batch = 0;
57
+
58
+ if (session->role == NIPC_UDS_ROLE_CLIENT &&
59
+ hdr->kind == NIPC_KIND_REQUEST) {
60
+ *max_payload = session->max_request_payload_bytes;
61
+ *max_batch = session->max_request_batch_items;
62
+ } else if (session->role == NIPC_UDS_ROLE_SERVER &&
63
+ hdr->kind == NIPC_KIND_RESPONSE) {
64
+ *max_payload = session->max_response_payload_bytes;
65
+ *max_batch = session->max_response_batch_items;
66
+ }
67
+}
68
+
69
+static nipc_uds_error_t validate_outbound_limits(nipc_uds_session_t *session,
70
+ const nipc_header_t *hdr,
71
+ size_t payload_len,
72
+ bool tracked)
73
+{
74
+ uint32_t max_payload;
75
+ uint32_t max_batch;
76
+ outbound_limits(session, hdr, &max_payload, &max_batch);
77
+
78
+ bool payload_fits_u32 = payload_len <= UINT32_MAX;
79
+ bool payload_within_limit = max_payload == 0 || payload_len <= max_payload;
80
+ bool batch_within_limit = max_batch == 0 || hdr->item_count <= max_batch;
81
+
82
+ if (payload_fits_u32 && payload_within_limit && batch_within_limit)
83
+ return NIPC_UDS_OK;
84
+
85
+ if (tracked)
86
+ nipc_uds_inflight_remove(session, hdr->message_id);
87
+ return NIPC_UDS_ERR_LIMIT_EXCEEDED;
88
+}
89
+
90
+static void fill_envelope(nipc_header_t *hdr, size_t payload_len)
91
+{
92
+ hdr->magic = NIPC_MAGIC_MSG;
93
+ hdr->version = NIPC_VERSION;
94
+ hdr->header_len = NIPC_HEADER_LEN;
95
+ /* validate_outbound_limits() rejects payloads that cannot fit the header. */
96
+ assert(payload_len <= UINT32_MAX);
97
+ hdr->payload_len = (uint32_t)payload_len;
98
+}
99
+
100
+static nipc_uds_error_t send_single_packet(nipc_uds_session_t *session,
101
+ nipc_header_t *hdr,
102
+ const void *payload,
103
+ size_t payload_len,
104
+ bool tracked)
105
+{
106
+ uint8_t hdr_buf[NIPC_HEADER_LEN];
107
+ nipc_header_encode(hdr, hdr_buf, sizeof(hdr_buf));
108
+
109
+ nipc_uds_error_t err = nipc_uds_raw_send_iov(
110
+ session->fd, hdr_buf, NIPC_HEADER_LEN, payload, payload_len);
111
+ update_inflight_after_send(session, hdr, tracked, err);
112
+ return err;
113
+}
114
+
115
+static nipc_uds_error_t send_first_chunk(nipc_uds_session_t *session,
116
+ nipc_header_t *hdr,
117
+ const void *payload,
118
+ size_t first_chunk_payload,
119
+ bool tracked)
120
+{
121
+ uint8_t hdr_buf[NIPC_HEADER_LEN];
122
+ nipc_header_encode(hdr, hdr_buf, sizeof(hdr_buf));
123
+
124
+ nipc_uds_error_t err = nipc_uds_raw_send_iov(
125
+ session->fd, hdr_buf, NIPC_HEADER_LEN, payload, first_chunk_payload);
126
+ update_inflight_after_send(session, hdr, tracked, err);
127
+ return err;
128
+}
129
+
130
+static nipc_uds_error_t send_continuation_chunk(nipc_uds_session_t *session,
131
+ nipc_header_t *hdr,
132
+ const uint8_t *src,
133
+ size_t this_chunk,
134
+ uint32_t chunk_index,
135
+ uint32_t chunk_count,
136
+ uint32_t total_msg,
137
+ bool tracked)
138
+{
139
+ /* packet_size is negotiated as uint32_t, so continuation chunks fit u32. */
140
+ assert(this_chunk <= UINT32_MAX);
141
+
142
+ nipc_chunk_header_t chk = {
143
+ .magic = NIPC_MAGIC_CHUNK,
144
+ .version = NIPC_VERSION,
145
+ .message_id = hdr->message_id,
146
+ .total_message_len = total_msg,
147
+ .chunk_index = chunk_index,
148
+ .chunk_count = chunk_count,
149
+ .chunk_payload_len = (uint32_t)this_chunk,
150
+ };
151
+
152
+ uint8_t chk_buf[NIPC_HEADER_LEN];
153
+ nipc_chunk_header_encode(&chk, chk_buf, sizeof(chk_buf));
154
+
155
+ nipc_uds_error_t err = nipc_uds_raw_send_iov(
156
+ session->fd, chk_buf, NIPC_HEADER_LEN, src, this_chunk);
157
+ update_inflight_after_send(session, hdr, tracked, err);
158
+ return err;
159
+}
160
+
161
+static nipc_uds_error_t send_chunked(nipc_uds_session_t *session,
162
+ nipc_header_t *hdr,
163
+ const void *payload,
164
+ size_t payload_len,
165
+ size_t total_msg,
166
+ bool tracked)
167
+{
168
+ if (session->packet_size <= NIPC_HEADER_LEN)
169
+ return NIPC_UDS_ERR_BAD_PARAM;
170
+
171
+ /* nipc_uds_header_payload_len() rejects totals wider than the wire field. */
172
+ assert(total_msg <= UINT32_MAX);
173
+ uint32_t total_msg_u32 = (uint32_t)total_msg;
174
+
175
+ size_t chunk_payload_budget = session->packet_size - NIPC_HEADER_LEN;
176
+ size_t remaining = payload_len;
177
+ size_t first_chunk_payload = min_of_size_t(remaining, chunk_payload_budget);
178
+ remaining -= first_chunk_payload;
179
+
180
+ uint32_t continuation_chunks = 0;
181
+ if (remaining > 0) {
182
+ continuation_chunks = (uint32_t)(1 + ((remaining - 1)
183
+ / chunk_payload_budget));
184
+ }
185
+ uint32_t chunk_count = 1 + continuation_chunks;
186
+
187
+ nipc_uds_error_t err = send_first_chunk(session, hdr, payload,
188
+ first_chunk_payload, tracked);
189
+ if (err != NIPC_UDS_OK)
190
+ return err;
191
+
192
+ const uint8_t *src = (const uint8_t *)payload + first_chunk_payload;
193
+ remaining = payload_len - first_chunk_payload;
194
+
195
+ for (uint32_t ci = 1; ci < chunk_count; ci++) {
196
+ size_t this_chunk = min_of_size_t(remaining, chunk_payload_budget);
197
+
198
+ err = send_continuation_chunk(session, hdr, src, this_chunk, ci,
199
+ chunk_count, total_msg_u32,
200
+ tracked);
201
+ if (err != NIPC_UDS_OK)
202
+ return err;
203
+
204
+ src += this_chunk;
205
+ remaining -= this_chunk;
206
+ }
207
+
208
+ return NIPC_UDS_OK;
209
+}
210
+
211
+nipc_uds_error_t nipc_uds_send(nipc_uds_session_t *session,
212
+ nipc_header_t *hdr,
213
+ const void *payload,
214
+ size_t payload_len)
215
+{
216
+ if (!session || session->fd < 0)
217
+ return NIPC_UDS_ERR_BAD_PARAM;
218
+
219
+ bool tracked = tracks_client_request(session, hdr);
220
+ nipc_uds_error_t err = track_outbound_request(session, hdr, tracked);
221
+ if (err != NIPC_UDS_OK)
222
+ return err;
223
+
224
+ err = validate_outbound_limits(session, hdr, payload_len, tracked);
225
+ if (err != NIPC_UDS_OK)
226
+ return err;
227
+
228
+ fill_envelope(hdr, payload_len);
229
+
230
+ size_t total_msg;
231
+ if (!nipc_uds_header_payload_len(payload_len, &total_msg)) {
232
+ if (tracked)
233
+ nipc_uds_inflight_remove(session, hdr->message_id);
234
+ return NIPC_UDS_ERR_LIMIT_EXCEEDED;
235
+ }
236
+
237
+ if (total_msg <= session->packet_size) {
238
+ return send_single_packet(session, hdr, payload, payload_len,
239
+ tracked);
240
+ }
241
+
242
+ return send_chunked(session, hdr, payload, payload_len, total_msg,
243
+ tracked);
244
+}
src/libnetdata/netipc/src/transport/windows/netipc_named_pipe.c
+25
-12
@@ -11,6 +11,7 @@
11
#include "netipc/netipc_named_pipe.h"
12
#include "netipc/netipc_protocol.h"
13
14
+#include <assert.h>
15
#include <stdio.h>
16
#include <stdlib.h>
17
#include <string.h>
@@ -50,12 +51,14 @@ static inline uint32_t apply_default(uint32_t val, uint32_t def)
51
52
static bool header_payload_len(size_t payload_len, size_t *msg_len_out)
53
{
53
-#if SIZE_MAX <= UINT32_MAX
54
if (payload_len > SIZE_MAX - NIPC_HEADER_LEN)
55
return false;
56
-#endif
56
58
- *msg_len_out = NIPC_HEADER_LEN + payload_len;
57
+ size_t msg_len = NIPC_HEADER_LEN + payload_len;
58
+ if (msg_len > UINT32_MAX)
59
+ return false;
60
+
61
+ *msg_len_out = msg_len;
62
return true;
63
}
64
@@ -936,6 +939,8 @@ nipc_np_error_t nipc_np_send(nipc_np_session_t *session,
939
hdr->magic = NIPC_MAGIC_MSG;
940
hdr->version = NIPC_VERSION;
941
hdr->header_len = NIPC_HEADER_LEN;
942
+ /* validate_outbound_limits() rejects payloads that cannot fit the header. */
943
+ assert(payload_len <= UINT32_MAX);
944
hdr->payload_len = (uint32_t)payload_len;
945
946
size_t total_msg;
@@ -961,12 +966,16 @@ nipc_np_error_t nipc_np_send(nipc_np_session_t *session,
966
}
967
968
/* Chunked send */
964
- size_t chunk_payload_budget = session->packet_size - NIPC_HEADER_LEN;
965
- if (chunk_payload_budget == 0) {
969
+ if (session->packet_size <= NIPC_HEADER_LEN) {
970
if (tracked) inflight_remove(session, hdr->message_id);
971
return NIPC_NP_ERR_BAD_PARAM;
972
}
973
974
+ /* header_payload_len() rejects totals wider than the wire field. */
975
+ assert(total_msg <= UINT32_MAX);
976
+ uint32_t total_msg_u32 = (uint32_t)total_msg;
977
+
978
+ size_t chunk_payload_budget = session->packet_size - NIPC_HEADER_LEN;
979
size_t remaining = payload_len;
980
size_t first_chunk_payload = remaining < chunk_payload_budget
981
? remaining : chunk_payload_budget;
@@ -974,8 +983,8 @@ nipc_np_error_t nipc_np_send(nipc_np_session_t *session,
983
remaining -= first_chunk_payload;
984
uint32_t continuation_chunks = 0;
985
if (remaining > 0) {
977
- continuation_chunks = (uint32_t)((remaining + chunk_payload_budget - 1)
978
- / chunk_payload_budget);
986
+ continuation_chunks = (uint32_t)(1 + ((remaining - 1)
987
+ / chunk_payload_budget));
988
}
989
uint32_t chunk_count = 1 + continuation_chunks;
990
@@ -1002,13 +1011,15 @@ nipc_np_error_t nipc_np_send(nipc_np_session_t *session,
1011
for (uint32_t ci = 1; ci < chunk_count; ci++) {
1012
size_t this_chunk = remaining < chunk_payload_budget
1013
? remaining : chunk_payload_budget;
1014
+ /* packet_size is negotiated as uint32_t, so continuation chunks fit u32. */
1015
+ assert(this_chunk <= UINT32_MAX);
1016
1017
nipc_chunk_header_t chk = {
1018
.magic = NIPC_MAGIC_CHUNK,
1019
.version = NIPC_VERSION,
1020
.flags = 0,
1021
.message_id = hdr->message_id,
1011
- .total_message_len = (uint32_t)total_msg,
1022
+ .total_message_len = total_msg_u32,
1023
.chunk_index = ci,
1024
.chunk_count = chunk_count,
1025
.chunk_payload_len = (uint32_t)this_chunk,
@@ -1126,8 +1137,11 @@ nipc_np_error_t nipc_np_receive(nipc_np_session_t *session,
1137
if (!header_payload_len(hdr_out->payload_len, &total_msg))
1138
return NIPC_NP_ERR_LIMIT_EXCEEDED;
1139
1140
+ if (n > total_msg)
1141
+ return NIPC_NP_ERR_PROTOCOL;
1142
+
1143
/* Non-chunked: entire message in one read */
1130
- if (n >= total_msg) {
1144
+ if (n == total_msg) {
1145
*payload_out = (const uint8_t *)buf + NIPC_HEADER_LEN;
1146
*payload_len_out = hdr_out->payload_len;
1147
@@ -1155,9 +1169,8 @@ nipc_np_error_t nipc_np_receive(nipc_np_session_t *session,
1169
size_t remaining_after_first = hdr_out->payload_len - first_payload_bytes;
1170
uint32_t expected_continuations = 0;
1171
if (remaining_after_first > 0 && chunk_payload_budget > 0) {
1158
- expected_continuations = (uint32_t)((remaining_after_first +
1159
- chunk_payload_budget - 1)
1160
- / chunk_payload_budget);
1172
+ expected_continuations = (uint32_t)(1 + ((remaining_after_first - 1)
1173
+ / chunk_payload_budget));
1174
}
1175
uint32_t expected_chunk_count = 1 + expected_continuations;
1176