1use std::io::{Read, Write};
8use std::num::NonZeroUsize;
9
10use interprocess::local_socket::traits::{Listener, Stream as _};
11use prost::Message;
12
13use crate::broker::protocol::{
14 read_frame, write_frame, AdminReply, ErrorCode, Frame, FramingError, HelloReply,
15 MAX_HELLO_BYTES,
16};
17
18use super::admin::{handle_admin_frame, AdminFrameError, AdminSnapshot, ADMIN_PAYLOAD_PROTOCOL};
19use super::connection::{
20 bind_local_socket, peer_identity_from_stream, refused_reply, reply_for_framing_error,
21 write_response_frame, BrokerConnectionError, HelloResponder, LocalSocketCleanup,
22 PeerCredentialPolicy,
23};
24use super::fd_pressure::{FdPressureDecision, FdPressureGuard};
25use super::hello_handler::PeerIdentity;
26
27const CONTROL_IO_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(5);
29
30const DEFAULT_HELLO_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
36const HELLO_READ_TIMEOUT_ENV: &str = "RUNNING_PROCESS_BROKER_HELLO_TIMEOUT_MS";
37
38fn hello_read_deadline() -> std::time::Instant {
39 let timeout = std::env::var(HELLO_READ_TIMEOUT_ENV)
40 .ok()
41 .and_then(|raw| raw.trim().parse::<u64>().ok())
42 .filter(|&ms| ms > 0)
43 .map(std::time::Duration::from_millis)
44 .unwrap_or(DEFAULT_HELLO_READ_TIMEOUT);
45 std::time::Instant::now() + timeout
46}
47
48struct DeadlineStream<'a, S> {
54 inner: &'a mut S,
55 deadline: std::time::Instant,
56}
57
58impl<'a, S> DeadlineStream<'a, S> {
59 fn new(inner: &'a mut S, deadline: std::time::Instant) -> Self {
60 Self { inner, deadline }
61 }
62
63 fn wait(&self) -> std::io::Result<()> {
64 let now = std::time::Instant::now();
65 if now >= self.deadline {
66 return Err(std::io::Error::new(
67 std::io::ErrorKind::TimedOut,
68 "broker control-socket peer did not send a complete frame in time",
69 ));
70 }
71 std::thread::sleep((self.deadline - now).min(CONTROL_IO_POLL_INTERVAL));
72 Ok(())
73 }
74}
75
76impl<S: Read> Read for DeadlineStream<'_, S> {
77 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
78 loop {
79 match self.inner.read(buf) {
80 Ok(0) => self.wait()?,
81 Ok(n) => return Ok(n),
82 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => self.wait()?,
83 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
84 Err(e) => return Err(e),
85 }
86 }
87 }
88}
89
90impl<S: Write> Write for DeadlineStream<'_, S> {
91 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
92 loop {
93 match self.inner.write(buf) {
94 Ok(0) => self.wait()?,
95 Ok(n) => return Ok(n),
96 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => self.wait()?,
97 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
98 Err(e) => return Err(e),
99 }
100 }
101 }
102
103 fn flush(&mut self) -> std::io::Result<()> {
104 loop {
105 match self.inner.flush() {
106 Ok(()) => return Ok(()),
107 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => self.wait()?,
108 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
109 Err(e) => return Err(e),
110 }
111 }
112 }
113}
114
115#[derive(Clone, Debug, PartialEq)]
117pub enum ControlSocketReply {
118 DroppedPeer,
120 Hello(HelloReply),
122 Admin(AdminReply),
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Eq)]
128pub enum ControlSocketConnectionLimit {
129 Bounded(NonZeroUsize),
131 Unbounded,
133}
134
135impl ControlSocketConnectionLimit {
136 fn should_continue(self, accepted: usize) -> bool {
137 match self {
138 Self::Bounded(limit) => accepted < limit.get(),
139 Self::Unbounded => true,
140 }
141 }
142}
143
144pub fn handle_control_connection_with_peer_policy<S, R, F>(
146 stream: &mut S,
147 hello_responder: &R,
148 snapshot_provider: &F,
149 peer: PeerIdentity,
150 peer_policy: &PeerCredentialPolicy,
151) -> Result<ControlSocketReply, ControlSocketError>
152where
153 S: Read + Write,
154 R: HelloResponder + ?Sized,
155 F: Fn() -> AdminSnapshot + ?Sized,
156{
157 handle_control_connection_with_peer_policy_and_fd_guard(
158 stream,
159 hello_responder,
160 snapshot_provider,
161 peer,
162 peer_policy,
163 None,
164 )
165}
166
167pub fn handle_control_connection_with_peer_policy_and_fd_guard<S, R, F>(
172 stream: &mut S,
173 hello_responder: &R,
174 snapshot_provider: &F,
175 peer: PeerIdentity,
176 peer_policy: &PeerCredentialPolicy,
177 fd_guard: Option<&FdPressureGuard>,
178) -> Result<ControlSocketReply, ControlSocketError>
179where
180 S: Read + Write,
181 R: HelloResponder + ?Sized,
182 F: Fn() -> AdminSnapshot + ?Sized,
183{
184 if !peer_policy.allows(&peer) {
185 return Ok(ControlSocketReply::DroppedPeer);
186 }
187
188 let request_bytes = match read_frame(stream) {
189 Ok(bytes) => bytes,
190 Err(err) => {
191 let reply = reply_for_framing_error(&err);
192 write_response_frame(stream, None, &reply)?;
193 return Ok(ControlSocketReply::Hello(reply));
194 }
195 };
196
197 let request_frame = match Frame::decode(request_bytes.as_slice()) {
198 Ok(frame) => frame,
199 Err(_) => {
200 let reply = refused_reply(ErrorCode::ErrorPeerRejected, "malformed broker Frame", 0);
201 write_response_frame(stream, None, &reply)?;
202 return Ok(ControlSocketReply::Hello(reply));
203 }
204 };
205
206 if request_frame.payload_protocol == ADMIN_PAYLOAD_PROTOCOL {
207 let snapshot = snapshot_provider();
208 let response_frame = handle_admin_frame(request_frame, &snapshot)?;
209 let reply = write_admin_response_frame(stream, &response_frame)?;
210 return Ok(ControlSocketReply::Admin(reply));
211 }
212
213 let reply = if request_bytes.len() > MAX_HELLO_BYTES {
214 refused_reply(
215 ErrorCode::ErrorPeerRejected,
216 "initial Hello frame exceeds 64 KiB",
217 0,
218 )
219 } else if let Some(guard) = fd_guard.filter(|guard| guard.is_demoted()) {
220 guard.refusal_reply()
221 } else {
222 hello_responder.handle_frame(request_frame.clone(), peer)
223 };
224 write_response_frame(stream, Some(&request_frame), &reply)?;
225 Ok(ControlSocketReply::Hello(reply))
226}
227
228pub fn serve_control_socket_connections_with_policy<R, F>(
231 socket_path: &str,
232 hello_responder: &R,
233 snapshot_provider: F,
234 connection_count: usize,
235 peer_policy: &PeerCredentialPolicy,
236) -> Result<(), ControlSocketError>
237where
238 R: HelloResponder + ?Sized,
239 F: Fn() -> AdminSnapshot,
240{
241 let Some(connection_count) = NonZeroUsize::new(connection_count) else {
242 return Ok(());
243 };
244
245 serve_control_socket_connections_with_limit_and_policy(
246 socket_path,
247 hello_responder,
248 snapshot_provider,
249 ControlSocketConnectionLimit::Bounded(connection_count),
250 peer_policy,
251 )
252}
253
254pub fn serve_control_socket_connections_with_limit_and_policy<R, F>(
257 socket_path: &str,
258 hello_responder: &R,
259 snapshot_provider: F,
260 connection_limit: ControlSocketConnectionLimit,
261 peer_policy: &PeerCredentialPolicy,
262) -> Result<(), ControlSocketError>
263where
264 R: HelloResponder + ?Sized,
265 F: Fn() -> AdminSnapshot,
266{
267 serve_control_socket_connections_with_limit_policy_and_post_hello(
268 socket_path,
269 hello_responder,
270 snapshot_provider,
271 connection_limit,
272 peer_policy,
273 |_stream, _reply| {},
274 )
275}
276
277pub fn serve_control_socket_connections_with_limit_policy_and_post_hello<R, F, H>(
284 socket_path: &str,
285 hello_responder: &R,
286 snapshot_provider: F,
287 connection_limit: ControlSocketConnectionLimit,
288 peer_policy: &PeerCredentialPolicy,
289 post_hello: H,
290) -> Result<(), ControlSocketError>
291where
292 R: HelloResponder + ?Sized,
293 F: Fn() -> AdminSnapshot,
294 H: FnMut(&mut interprocess::local_socket::Stream, &HelloReply),
295{
296 let fd_guard = FdPressureGuard::default();
297 serve_control_socket_connections_with_limit_policy_post_hello_and_fd_guard(
298 socket_path,
299 hello_responder,
300 snapshot_provider,
301 connection_limit,
302 peer_policy,
303 post_hello,
304 &fd_guard,
305 )
306}
307
308#[allow(clippy::too_many_arguments)]
317pub fn serve_control_socket_connections_with_limit_policy_post_hello_and_fd_guard<R, F, H>(
318 socket_path: &str,
319 hello_responder: &R,
320 snapshot_provider: F,
321 connection_limit: ControlSocketConnectionLimit,
322 peer_policy: &PeerCredentialPolicy,
323 mut post_hello: H,
324 fd_guard: &FdPressureGuard,
325) -> Result<(), ControlSocketError>
326where
327 R: HelloResponder + ?Sized,
328 F: Fn() -> AdminSnapshot,
329 H: FnMut(&mut interprocess::local_socket::Stream, &HelloReply),
330{
331 const FD_PRESSURE_ACCEPT_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
334
335 let listener = bind_local_socket(socket_path)?;
336 let cleanup = LocalSocketCleanup(socket_path);
337 let result = (|| {
338 let mut accepted = 0;
339 while connection_limit.should_continue(accepted) {
340 let mut stream = match listener.accept() {
341 Ok(stream) => {
342 fd_guard.on_accept_ok();
343 stream
344 }
345 Err(err) => {
346 let was_demoted = fd_guard.is_demoted();
347 if fd_guard.on_accept_error(&err) == FdPressureDecision::Demoted {
348 if !was_demoted {
349 eprintln!(
350 "running-process-broker: accept on {socket_path} demoted \
351 under fd pressure: {err}"
352 );
353 }
354 accepted += 1;
355 std::thread::sleep(FD_PRESSURE_ACCEPT_BACKOFF);
356 continue;
357 }
358 return Err(BrokerConnectionError::Io(err).into());
359 }
360 };
361 accepted += 1;
362 let peer = peer_identity_from_stream(&stream)?;
363 let nonblocking_set = stream.set_nonblocking(true).is_ok();
369 let reply_result = {
370 let mut deadline_stream = DeadlineStream::new(&mut stream, hello_read_deadline());
371 handle_control_connection_with_peer_policy_and_fd_guard(
372 &mut deadline_stream,
373 hello_responder,
374 &snapshot_provider,
375 peer.clone(),
376 peer_policy,
377 Some(fd_guard),
378 )
379 };
380 if nonblocking_set {
381 let _ = stream.set_nonblocking(false);
382 }
383 let reply = reply_result?;
384 if reply == ControlSocketReply::DroppedPeer {
385 eprintln!(
386 "running-process-broker: dropped connection on {socket_path} from peer \
387 pid={} uid_or_sid={:?}: credential policy refused",
388 peer.pid, peer.uid_or_sid
389 );
390 }
391 if let ControlSocketReply::Hello(hello_reply) = &reply {
392 post_hello(&mut stream, hello_reply);
393 }
394 }
395 Ok(())
396 })();
397 drop(listener);
398 drop(cleanup);
399 result
400}
401
402fn write_admin_response_frame<W: Write>(
403 writer: &mut W,
404 response_frame: &Frame,
405) -> Result<AdminReply, ControlSocketError> {
406 let mut response_bytes = Vec::new();
407 response_frame
408 .encode(&mut response_bytes)
409 .map_err(ControlSocketError::EncodeFrame)?;
410 write_frame(writer, &response_bytes)?;
411 AdminReply::decode(response_frame.payload.as_slice())
412 .map_err(ControlSocketError::DecodeAdminReply)
413}
414
415#[derive(Debug, thiserror::Error)]
417pub enum ControlSocketError {
418 #[error(transparent)]
420 Connection(#[from] BrokerConnectionError),
421 #[error(transparent)]
423 Framing(#[from] FramingError),
424 #[error(transparent)]
426 AdminFrame(#[from] AdminFrameError),
427 #[error("failed to encode broker control response Frame: {0}")]
429 EncodeFrame(prost::EncodeError),
430 #[error("failed to decode admin reply payload: {0}")]
432 DecodeAdminReply(prost::DecodeError),
433}
434
435#[cfg(test)]
436mod cluster_g_tests {
437 use super::*;
438 use std::time::{Duration, Instant};
439
440 struct NeverReady;
441 impl Read for NeverReady {
442 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
443 Err(std::io::Error::new(
444 std::io::ErrorKind::WouldBlock,
445 "never ready",
446 ))
447 }
448 }
449 impl Write for NeverReady {
450 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
451 Ok(buf.len())
452 }
453 fn flush(&mut self) -> std::io::Result<()> {
454 Ok(())
455 }
456 }
457
458 #[test]
459 fn deadline_stream_read_times_out_on_silent_peer() {
460 let mut inner = NeverReady;
461 let mut ds = DeadlineStream::new(&mut inner, Instant::now() + Duration::from_millis(100));
462 let mut buf = [0u8; 4];
463 let start = Instant::now();
464 let err = ds.read(&mut buf).unwrap_err();
465 assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
466 assert!(start.elapsed() < Duration::from_secs(2), "must be bounded");
467 }
468
469 #[test]
470 fn deadline_stream_passes_ready_data_through() {
471 let data = b"hello";
472 let mut cursor = std::io::Cursor::new(data.to_vec());
473 let mut ds = DeadlineStream::new(&mut cursor, Instant::now() + Duration::from_secs(1));
474 let mut buf = [0u8; 5];
475 ds.read_exact(&mut buf).unwrap();
476 assert_eq!(&buf, data);
477 }
478}