1use std::io::{Read, Write};
8use std::num::NonZeroUsize;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::sync::{mpsc, Mutex};
11
12use prost::Message;
13
14use crate::broker::protocol::{
15 read_frame, write_frame, AdminReply, AdminRequest, AdminVerb, ErrorCode, Frame, FramingError,
16 HelloReply, MAX_HELLO_BYTES,
17};
18
19use super::admin::{handle_admin_frame, AdminFrameError, AdminSnapshot, ADMIN_PAYLOAD_PROTOCOL};
20use super::connection::{
21 bind_local_socket, peer_identity_from_stream, refused_reply, reply_for_framing_error,
22 write_response_frame, BrokerConnectionError, HelloResponder, LocalSocketCleanup,
23 PeerCredentialPolicy,
24};
25use super::deadline_stream::{hello_read_deadline, DeadlineStream};
26use super::fd_pressure::{FdPressureDecision, FdPressureGuard};
27use super::hello_handler::PeerIdentity;
28
29#[derive(Clone, Debug, PartialEq)]
31pub enum ControlSocketReply {
32 DroppedPeer,
34 Hello(HelloReply),
36 Admin(AdminReply),
38 ShutdownRequested,
42}
43
44fn admin_request_verb(frame: &Frame) -> Option<AdminVerb> {
47 AdminRequest::decode(frame.payload.as_slice())
48 .ok()
49 .and_then(|request| AdminVerb::try_from(request.verb).ok())
50}
51
52fn wake_control_socket_accept(socket_path: &str) {
56 if let Ok(endpoint) = crate::platform::ipc::Endpoint::new(socket_path.to_owned()) {
57 let _ = crate::platform::ipc::Stream::connect(&endpoint);
58 }
59}
60
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum ControlSocketConnectionLimit {
64 Bounded(NonZeroUsize),
66 Unbounded,
68}
69
70impl ControlSocketConnectionLimit {
71 fn should_continue(self, accepted: usize) -> bool {
72 match self {
73 Self::Bounded(limit) => accepted < limit.get(),
74 Self::Unbounded => true,
75 }
76 }
77}
78
79const LAUNCH_CONTROL_SOCKET_WORKERS: usize = 8;
80
81pub fn handle_control_connection_with_peer_policy<S, R, F>(
83 stream: &mut S,
84 hello_responder: &R,
85 snapshot_provider: &F,
86 peer: PeerIdentity,
87 peer_policy: &PeerCredentialPolicy,
88) -> Result<ControlSocketReply, ControlSocketError>
89where
90 S: Read + Write,
91 R: HelloResponder + ?Sized,
92 F: Fn() -> AdminSnapshot + ?Sized,
93{
94 handle_control_connection_with_peer_policy_and_fd_guard(
95 stream,
96 hello_responder,
97 snapshot_provider,
98 peer,
99 peer_policy,
100 None,
101 )
102}
103
104pub fn handle_control_connection_with_peer_policy_and_fd_guard<S, R, F>(
109 stream: &mut S,
110 hello_responder: &R,
111 snapshot_provider: &F,
112 peer: PeerIdentity,
113 peer_policy: &PeerCredentialPolicy,
114 fd_guard: Option<&FdPressureGuard>,
115) -> Result<ControlSocketReply, ControlSocketError>
116where
117 S: Read + Write,
118 R: HelloResponder + ?Sized,
119 F: Fn() -> AdminSnapshot + ?Sized,
120{
121 if !peer_policy.allows(&peer) {
122 return Ok(ControlSocketReply::DroppedPeer);
123 }
124
125 let request_bytes = match read_frame(stream) {
126 Ok(bytes) => bytes,
127 Err(err) => {
128 let reply = reply_for_framing_error(&err);
129 write_response_frame(stream, None, &reply)?;
130 return Ok(ControlSocketReply::Hello(reply));
131 }
132 };
133
134 let request_frame = match Frame::decode(request_bytes.as_slice()) {
135 Ok(frame) => frame,
136 Err(_) => {
137 let reply = refused_reply(ErrorCode::ErrorPeerRejected, "malformed broker Frame", 0);
138 write_response_frame(stream, None, &reply)?;
139 return Ok(ControlSocketReply::Hello(reply));
140 }
141 };
142
143 if request_frame.payload_protocol == ADMIN_PAYLOAD_PROTOCOL {
144 let is_shutdown = admin_request_verb(&request_frame) == Some(AdminVerb::Shutdown);
148 let snapshot = snapshot_provider();
149 let response_frame = handle_admin_frame(request_frame, &snapshot)?;
150 let reply = write_admin_response_frame(stream, &response_frame)?;
151 return Ok(if is_shutdown {
152 ControlSocketReply::ShutdownRequested
153 } else {
154 ControlSocketReply::Admin(reply)
155 });
156 }
157
158 let reply = if request_bytes.len() > MAX_HELLO_BYTES {
159 refused_reply(
160 ErrorCode::ErrorPeerRejected,
161 "initial Hello frame exceeds 64 KiB",
162 0,
163 )
164 } else if let Some(guard) = fd_guard.filter(|guard| guard.is_demoted()) {
165 guard.refusal_reply()
166 } else {
167 hello_responder.handle_frame(request_frame.clone(), peer)
168 };
169 write_response_frame(stream, Some(&request_frame), &reply)?;
170 Ok(ControlSocketReply::Hello(reply))
171}
172
173pub fn serve_control_socket_connections_with_policy<R, F>(
176 socket_path: &str,
177 hello_responder: &R,
178 snapshot_provider: F,
179 connection_count: usize,
180 peer_policy: &PeerCredentialPolicy,
181) -> Result<(), ControlSocketError>
182where
183 R: HelloResponder + ?Sized,
184 F: Fn() -> AdminSnapshot,
185{
186 let Some(connection_count) = NonZeroUsize::new(connection_count) else {
187 return Ok(());
188 };
189
190 serve_control_socket_connections_with_limit_and_policy(
191 socket_path,
192 hello_responder,
193 snapshot_provider,
194 ControlSocketConnectionLimit::Bounded(connection_count),
195 peer_policy,
196 )
197}
198
199pub fn serve_control_socket_connections_with_limit_and_policy<R, F>(
202 socket_path: &str,
203 hello_responder: &R,
204 snapshot_provider: F,
205 connection_limit: ControlSocketConnectionLimit,
206 peer_policy: &PeerCredentialPolicy,
207) -> Result<(), ControlSocketError>
208where
209 R: HelloResponder + ?Sized,
210 F: Fn() -> AdminSnapshot,
211{
212 serve_control_socket_connections_with_limit_policy_and_post_hello(
213 socket_path,
214 hello_responder,
215 snapshot_provider,
216 connection_limit,
217 peer_policy,
218 |_stream, _reply| {},
219 )
220}
221
222pub fn serve_control_socket_connections_with_limit_policy_and_post_hello<R, F, H>(
229 socket_path: &str,
230 hello_responder: &R,
231 snapshot_provider: F,
232 connection_limit: ControlSocketConnectionLimit,
233 peer_policy: &PeerCredentialPolicy,
234 post_hello: H,
235) -> Result<(), ControlSocketError>
236where
237 R: HelloResponder + ?Sized,
238 F: Fn() -> AdminSnapshot,
239 H: FnMut(&mut interprocess::local_socket::Stream, &HelloReply),
240{
241 let fd_guard = FdPressureGuard::default();
242 serve_control_socket_connections_with_limit_policy_post_hello_and_fd_guard(
243 socket_path,
244 hello_responder,
245 snapshot_provider,
246 connection_limit,
247 peer_policy,
248 post_hello,
249 &fd_guard,
250 )
251}
252
253#[allow(clippy::too_many_arguments)]
262pub fn serve_control_socket_connections_with_limit_policy_post_hello_and_fd_guard<R, F, H>(
263 socket_path: &str,
264 hello_responder: &R,
265 snapshot_provider: F,
266 connection_limit: ControlSocketConnectionLimit,
267 peer_policy: &PeerCredentialPolicy,
268 mut post_hello: H,
269 fd_guard: &FdPressureGuard,
270) -> Result<(), ControlSocketError>
271where
272 R: HelloResponder + ?Sized,
273 F: Fn() -> AdminSnapshot,
274 H: FnMut(&mut interprocess::local_socket::Stream, &HelloReply),
275{
276 serve_control_socket_connections_with_limit_policy_post_hello_opaque(
277 socket_path,
278 hello_responder,
279 snapshot_provider,
280 connection_limit,
281 peer_policy,
282 move |stream, reply| {
283 let mut legacy_stream =
284 running_process_platform_internal::into_legacy_ipc_stream(stream);
285 post_hello(&mut legacy_stream, reply);
286 },
287 fd_guard,
288 )
289}
290
291#[allow(clippy::too_many_arguments)]
297pub(super) fn serve_control_socket_connections_with_limit_policy_post_hello_opaque<R, F, H>(
298 socket_path: &str,
299 hello_responder: &R,
300 snapshot_provider: F,
301 connection_limit: ControlSocketConnectionLimit,
302 peer_policy: &PeerCredentialPolicy,
303 mut post_hello: H,
304 fd_guard: &FdPressureGuard,
305) -> Result<(), ControlSocketError>
306where
307 R: HelloResponder + ?Sized,
308 F: Fn() -> AdminSnapshot,
309 H: FnMut(crate::platform::ipc::Stream, &HelloReply),
310{
311 const FD_PRESSURE_ACCEPT_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
314
315 let listener = bind_local_socket(socket_path)?;
316 let cleanup = LocalSocketCleanup(socket_path);
317 let result = (|| {
318 let mut accepted = 0;
319 while connection_limit.should_continue(accepted) {
320 let mut stream = match listener.accept() {
321 Ok(stream) => {
322 fd_guard.on_accept_ok();
323 stream
324 }
325 Err(err) => {
326 let was_demoted = fd_guard.is_demoted();
327 if fd_guard.on_accept_error(&err) == FdPressureDecision::Demoted {
328 if !was_demoted {
329 eprintln!(
330 "running-process-broker: accept on {socket_path} demoted \
331 under fd pressure: {err}"
332 );
333 }
334 accepted += 1;
335 std::thread::sleep(FD_PRESSURE_ACCEPT_BACKOFF);
336 continue;
337 }
338 return Err(BrokerConnectionError::Io(err).into());
339 }
340 };
341 accepted += 1;
342 let peer = peer_identity_from_stream(&stream)?;
343 let nonblocking_set = stream.set_nonblocking(true).is_ok();
349 let reply_result = {
350 let mut deadline_stream = DeadlineStream::new(&mut stream, hello_read_deadline());
351 handle_control_connection_with_peer_policy_and_fd_guard(
352 &mut deadline_stream,
353 hello_responder,
354 &snapshot_provider,
355 peer.clone(),
356 peer_policy,
357 Some(fd_guard),
358 )
359 };
360 if nonblocking_set {
361 let _ = stream.set_nonblocking(false);
362 }
363 let reply = reply_result?;
364 if reply == ControlSocketReply::DroppedPeer {
365 eprintln!(
366 "running-process-broker: dropped connection on {socket_path} from peer \
367 pid={} uid_or_sid={:?}: credential policy refused",
368 peer.pid, peer.uid_or_sid
369 );
370 }
371 if let ControlSocketReply::Hello(hello_reply) = &reply {
372 post_hello(stream, hello_reply);
373 }
374 }
375 Ok(())
376 })();
377 drop(listener);
378 drop(cleanup);
379 result
380}
381
382pub(super) fn serve_launch_control_socket_connections_concurrently<R, F>(
389 socket_path: &str,
390 hello_responder: &R,
391 snapshot_provider: F,
392 connection_limit: ControlSocketConnectionLimit,
393 peer_policy: &PeerCredentialPolicy,
394 fd_guard: &FdPressureGuard,
395) -> Result<(), ControlSocketError>
396where
397 R: HelloResponder + Sync + ?Sized,
398 F: Fn() -> AdminSnapshot + Sync,
399{
400 const FD_PRESSURE_ACCEPT_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
401
402 let listener = bind_local_socket(socket_path)?;
403 let cleanup = LocalSocketCleanup(socket_path);
404 let bounded = matches!(connection_limit, ControlSocketConnectionLimit::Bounded(_));
405 let (job_sender, job_receiver) = mpsc::sync_channel(LAUNCH_CONTROL_SOCKET_WORKERS);
406 let job_receiver = Mutex::new(job_receiver);
407 let (result_sender, result_receiver) = mpsc::channel();
408 let shutdown = AtomicBool::new(false);
411 let result = std::thread::scope(|scope| {
412 let mut workers = Vec::with_capacity(LAUNCH_CONTROL_SOCKET_WORKERS);
413
414 for _ in 0..LAUNCH_CONTROL_SOCKET_WORKERS {
415 let result_sender = result_sender.clone();
416 let job_receiver = &job_receiver;
417 let snapshot_provider = &snapshot_provider;
418 let shutdown = &shutdown;
419 workers.push(scope.spawn(move || loop {
420 let job = {
421 let receiver = job_receiver
422 .lock()
423 .unwrap_or_else(|poisoned| poisoned.into_inner());
424 receiver.recv()
425 };
426 let Ok((mut stream, peer)) = job else {
427 break;
428 };
429 let outcome = handle_accepted_control_connection(
430 &mut stream,
431 hello_responder,
432 snapshot_provider,
433 peer,
434 peer_policy,
435 fd_guard,
436 );
437 if matches!(outcome, Ok(ControlSocketReply::ShutdownRequested)) {
438 shutdown.store(true, Ordering::SeqCst);
439 wake_control_socket_accept(socket_path);
442 }
443 let result = outcome.map(|_| ());
444 if bounded {
445 let _ = result_sender.send(result);
446 } else if let Err(error) = result {
447 eprintln!(
448 "running-process-broker: control connection failed on {socket_path}: {error}"
449 );
450 }
451 }));
452 }
453 drop(result_sender);
454
455 let mut accepted = 0;
456 let mut dispatched = 0;
457 let accept_result: Result<(), ControlSocketError> = loop {
458 if shutdown.load(Ordering::SeqCst) {
462 break Ok(());
463 }
464 if !connection_limit.should_continue(accepted) {
465 break Ok(());
466 }
467 let stream = match listener.accept() {
468 Ok(stream) => {
469 fd_guard.on_accept_ok();
470 stream
471 }
472 Err(error) => {
473 let was_demoted = fd_guard.is_demoted();
474 if fd_guard.on_accept_error(&error) == FdPressureDecision::Demoted {
475 if !was_demoted {
476 eprintln!(
477 "running-process-broker: accept on {socket_path} demoted \
478 under fd pressure: {error}"
479 );
480 }
481 accepted += 1;
482 std::thread::sleep(FD_PRESSURE_ACCEPT_BACKOFF);
483 continue;
484 }
485 break Err(BrokerConnectionError::Io(error).into());
486 }
487 };
488 if shutdown.load(Ordering::SeqCst) {
491 break Ok(());
492 }
493 accepted += 1;
494 let peer = match peer_identity_from_stream(&stream) {
495 Ok(peer) => peer,
496 Err(error) => break Err(error.into()),
497 };
498 if job_sender.send((stream, peer)).is_err() {
499 break Err(BrokerConnectionError::WorkerPanic.into());
500 }
501 dispatched += 1;
502 };
503 drop(job_sender);
504
505 let mut connection_error = None;
506 if bounded {
507 for _ in 0..dispatched {
508 match result_receiver.recv() {
509 Ok(Ok(())) => {}
510 Ok(Err(error)) if connection_error.is_none() => connection_error = Some(error),
511 Ok(Err(_)) => {}
512 Err(_) => break,
513 }
514 }
515 }
516
517 let mut worker_panicked = false;
518 for worker in workers {
519 worker_panicked |= worker.join().is_err();
520 }
521 if worker_panicked {
522 return Err(BrokerConnectionError::WorkerPanic.into());
523 }
524 accept_result?;
525 if let Some(error) = connection_error {
526 return Err(error);
527 }
528 Ok(())
529 });
530 drop(listener);
531 drop(cleanup);
532 result
533}
534
535fn handle_accepted_control_connection<R, F>(
536 stream: &mut crate::platform::ipc::Stream,
537 hello_responder: &R,
538 snapshot_provider: &F,
539 peer: PeerIdentity,
540 peer_policy: &PeerCredentialPolicy,
541 fd_guard: &FdPressureGuard,
542) -> Result<ControlSocketReply, ControlSocketError>
543where
544 R: HelloResponder + ?Sized,
545 F: Fn() -> AdminSnapshot + ?Sized,
546{
547 let peer_for_log = peer.clone();
548 let nonblocking_set = stream.set_nonblocking(true).is_ok();
549 let reply_result = {
550 let mut deadline_stream = DeadlineStream::new(stream, hello_read_deadline());
551 handle_control_connection_with_peer_policy_and_fd_guard(
552 &mut deadline_stream,
553 hello_responder,
554 snapshot_provider,
555 peer,
556 peer_policy,
557 Some(fd_guard),
558 )
559 };
560 if nonblocking_set {
561 let _ = stream.set_nonblocking(false);
562 }
563 let reply = reply_result?;
564 if reply == ControlSocketReply::DroppedPeer {
565 eprintln!(
566 "running-process-broker: dropped connection from peer pid={} uid_or_sid={:?}: \
567 credential policy refused",
568 peer_for_log.pid, peer_for_log.uid_or_sid
569 );
570 }
571 Ok(reply)
572}
573
574fn write_admin_response_frame<W: Write>(
575 writer: &mut W,
576 response_frame: &Frame,
577) -> Result<AdminReply, ControlSocketError> {
578 let mut response_bytes = Vec::new();
579 response_frame
580 .encode(&mut response_bytes)
581 .map_err(ControlSocketError::EncodeFrame)?;
582 write_frame(writer, &response_bytes)?;
583 AdminReply::decode(response_frame.payload.as_slice())
584 .map_err(ControlSocketError::DecodeAdminReply)
585}
586
587#[derive(Debug, thiserror::Error)]
589pub enum ControlSocketError {
590 #[error(transparent)]
592 Connection(#[from] BrokerConnectionError),
593 #[error(transparent)]
595 Framing(#[from] FramingError),
596 #[error(transparent)]
598 AdminFrame(#[from] AdminFrameError),
599 #[error("failed to encode broker control response Frame: {0}")]
601 EncodeFrame(prost::EncodeError),
602 #[error("failed to decode admin reply payload: {0}")]
604 DecodeAdminReply(prost::DecodeError),
605}
606
607#[cfg(test)]
608mod cluster_g_tests {
609 use super::*;
610 use std::time::{Duration, Instant};
611
612 struct NeverReady;
613 impl Read for NeverReady {
614 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
615 Err(std::io::Error::new(
616 std::io::ErrorKind::WouldBlock,
617 "never ready",
618 ))
619 }
620 }
621 impl Write for NeverReady {
622 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
623 Ok(buf.len())
624 }
625 fn flush(&mut self) -> std::io::Result<()> {
626 Ok(())
627 }
628 }
629
630 #[test]
631 fn deadline_stream_read_times_out_on_silent_peer() {
632 let mut inner = NeverReady;
633 let mut ds = DeadlineStream::new(&mut inner, Instant::now() + Duration::from_millis(100));
634 let mut buf = [0u8; 4];
635 let start = Instant::now();
636 let err = ds.read(&mut buf).unwrap_err();
637 assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
638 assert!(start.elapsed() < Duration::from_secs(2), "must be bounded");
639 }
640
641 #[test]
642 fn deadline_stream_passes_ready_data_through() {
643 let data = b"hello";
644 let mut cursor = std::io::Cursor::new(data.to_vec());
645 let mut ds = DeadlineStream::new(&mut cursor, Instant::now() + Duration::from_secs(1));
646 let mut buf = [0u8; 5];
647 ds.read_exact(&mut buf).unwrap();
648 assert_eq!(&buf, data);
649 }
650}
651
652#[cfg(test)]
653#[path = "../../tests/control_socket_coverage.rs"]
654mod coverage_tests;