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::deadline_stream::{hello_read_deadline, DeadlineStream};
25use super::fd_pressure::{FdPressureDecision, FdPressureGuard};
26use super::hello_handler::PeerIdentity;
27
28#[derive(Clone, Debug, PartialEq)]
30pub enum ControlSocketReply {
31 DroppedPeer,
33 Hello(HelloReply),
35 Admin(AdminReply),
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum ControlSocketConnectionLimit {
42 Bounded(NonZeroUsize),
44 Unbounded,
46}
47
48impl ControlSocketConnectionLimit {
49 fn should_continue(self, accepted: usize) -> bool {
50 match self {
51 Self::Bounded(limit) => accepted < limit.get(),
52 Self::Unbounded => true,
53 }
54 }
55}
56
57pub fn handle_control_connection_with_peer_policy<S, R, F>(
59 stream: &mut S,
60 hello_responder: &R,
61 snapshot_provider: &F,
62 peer: PeerIdentity,
63 peer_policy: &PeerCredentialPolicy,
64) -> Result<ControlSocketReply, ControlSocketError>
65where
66 S: Read + Write,
67 R: HelloResponder + ?Sized,
68 F: Fn() -> AdminSnapshot + ?Sized,
69{
70 handle_control_connection_with_peer_policy_and_fd_guard(
71 stream,
72 hello_responder,
73 snapshot_provider,
74 peer,
75 peer_policy,
76 None,
77 )
78}
79
80pub fn handle_control_connection_with_peer_policy_and_fd_guard<S, R, F>(
85 stream: &mut S,
86 hello_responder: &R,
87 snapshot_provider: &F,
88 peer: PeerIdentity,
89 peer_policy: &PeerCredentialPolicy,
90 fd_guard: Option<&FdPressureGuard>,
91) -> Result<ControlSocketReply, ControlSocketError>
92where
93 S: Read + Write,
94 R: HelloResponder + ?Sized,
95 F: Fn() -> AdminSnapshot + ?Sized,
96{
97 if !peer_policy.allows(&peer) {
98 return Ok(ControlSocketReply::DroppedPeer);
99 }
100
101 let request_bytes = match read_frame(stream) {
102 Ok(bytes) => bytes,
103 Err(err) => {
104 let reply = reply_for_framing_error(&err);
105 write_response_frame(stream, None, &reply)?;
106 return Ok(ControlSocketReply::Hello(reply));
107 }
108 };
109
110 let request_frame = match Frame::decode(request_bytes.as_slice()) {
111 Ok(frame) => frame,
112 Err(_) => {
113 let reply = refused_reply(ErrorCode::ErrorPeerRejected, "malformed broker Frame", 0);
114 write_response_frame(stream, None, &reply)?;
115 return Ok(ControlSocketReply::Hello(reply));
116 }
117 };
118
119 if request_frame.payload_protocol == ADMIN_PAYLOAD_PROTOCOL {
120 let snapshot = snapshot_provider();
121 let response_frame = handle_admin_frame(request_frame, &snapshot)?;
122 let reply = write_admin_response_frame(stream, &response_frame)?;
123 return Ok(ControlSocketReply::Admin(reply));
124 }
125
126 let reply = if request_bytes.len() > MAX_HELLO_BYTES {
127 refused_reply(
128 ErrorCode::ErrorPeerRejected,
129 "initial Hello frame exceeds 64 KiB",
130 0,
131 )
132 } else if let Some(guard) = fd_guard.filter(|guard| guard.is_demoted()) {
133 guard.refusal_reply()
134 } else {
135 hello_responder.handle_frame(request_frame.clone(), peer)
136 };
137 write_response_frame(stream, Some(&request_frame), &reply)?;
138 Ok(ControlSocketReply::Hello(reply))
139}
140
141pub fn serve_control_socket_connections_with_policy<R, F>(
144 socket_path: &str,
145 hello_responder: &R,
146 snapshot_provider: F,
147 connection_count: usize,
148 peer_policy: &PeerCredentialPolicy,
149) -> Result<(), ControlSocketError>
150where
151 R: HelloResponder + ?Sized,
152 F: Fn() -> AdminSnapshot,
153{
154 let Some(connection_count) = NonZeroUsize::new(connection_count) else {
155 return Ok(());
156 };
157
158 serve_control_socket_connections_with_limit_and_policy(
159 socket_path,
160 hello_responder,
161 snapshot_provider,
162 ControlSocketConnectionLimit::Bounded(connection_count),
163 peer_policy,
164 )
165}
166
167pub fn serve_control_socket_connections_with_limit_and_policy<R, F>(
170 socket_path: &str,
171 hello_responder: &R,
172 snapshot_provider: F,
173 connection_limit: ControlSocketConnectionLimit,
174 peer_policy: &PeerCredentialPolicy,
175) -> Result<(), ControlSocketError>
176where
177 R: HelloResponder + ?Sized,
178 F: Fn() -> AdminSnapshot,
179{
180 serve_control_socket_connections_with_limit_policy_and_post_hello(
181 socket_path,
182 hello_responder,
183 snapshot_provider,
184 connection_limit,
185 peer_policy,
186 |_stream, _reply| {},
187 )
188}
189
190pub fn serve_control_socket_connections_with_limit_policy_and_post_hello<R, F, H>(
197 socket_path: &str,
198 hello_responder: &R,
199 snapshot_provider: F,
200 connection_limit: ControlSocketConnectionLimit,
201 peer_policy: &PeerCredentialPolicy,
202 post_hello: H,
203) -> Result<(), ControlSocketError>
204where
205 R: HelloResponder + ?Sized,
206 F: Fn() -> AdminSnapshot,
207 H: FnMut(&mut interprocess::local_socket::Stream, &HelloReply),
208{
209 let fd_guard = FdPressureGuard::default();
210 serve_control_socket_connections_with_limit_policy_post_hello_and_fd_guard(
211 socket_path,
212 hello_responder,
213 snapshot_provider,
214 connection_limit,
215 peer_policy,
216 post_hello,
217 &fd_guard,
218 )
219}
220
221#[allow(clippy::too_many_arguments)]
230pub fn serve_control_socket_connections_with_limit_policy_post_hello_and_fd_guard<R, F, H>(
231 socket_path: &str,
232 hello_responder: &R,
233 snapshot_provider: F,
234 connection_limit: ControlSocketConnectionLimit,
235 peer_policy: &PeerCredentialPolicy,
236 mut post_hello: H,
237 fd_guard: &FdPressureGuard,
238) -> Result<(), ControlSocketError>
239where
240 R: HelloResponder + ?Sized,
241 F: Fn() -> AdminSnapshot,
242 H: FnMut(&mut interprocess::local_socket::Stream, &HelloReply),
243{
244 const FD_PRESSURE_ACCEPT_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
247
248 let listener = bind_local_socket(socket_path)?;
249 let cleanup = LocalSocketCleanup(socket_path);
250 let result = (|| {
251 let mut accepted = 0;
252 while connection_limit.should_continue(accepted) {
253 let mut stream = match listener.accept() {
254 Ok(stream) => {
255 fd_guard.on_accept_ok();
256 stream
257 }
258 Err(err) => {
259 let was_demoted = fd_guard.is_demoted();
260 if fd_guard.on_accept_error(&err) == FdPressureDecision::Demoted {
261 if !was_demoted {
262 eprintln!(
263 "running-process-broker: accept on {socket_path} demoted \
264 under fd pressure: {err}"
265 );
266 }
267 accepted += 1;
268 std::thread::sleep(FD_PRESSURE_ACCEPT_BACKOFF);
269 continue;
270 }
271 return Err(BrokerConnectionError::Io(err).into());
272 }
273 };
274 accepted += 1;
275 let peer = peer_identity_from_stream(&stream)?;
276 let nonblocking_set = stream.set_nonblocking(true).is_ok();
282 let reply_result = {
283 let mut deadline_stream = DeadlineStream::new(&mut stream, hello_read_deadline());
284 handle_control_connection_with_peer_policy_and_fd_guard(
285 &mut deadline_stream,
286 hello_responder,
287 &snapshot_provider,
288 peer.clone(),
289 peer_policy,
290 Some(fd_guard),
291 )
292 };
293 if nonblocking_set {
294 let _ = stream.set_nonblocking(false);
295 }
296 let reply = reply_result?;
297 if reply == ControlSocketReply::DroppedPeer {
298 eprintln!(
299 "running-process-broker: dropped connection on {socket_path} from peer \
300 pid={} uid_or_sid={:?}: credential policy refused",
301 peer.pid, peer.uid_or_sid
302 );
303 }
304 if let ControlSocketReply::Hello(hello_reply) = &reply {
305 post_hello(&mut stream, hello_reply);
306 }
307 }
308 Ok(())
309 })();
310 drop(listener);
311 drop(cleanup);
312 result
313}
314
315fn write_admin_response_frame<W: Write>(
316 writer: &mut W,
317 response_frame: &Frame,
318) -> Result<AdminReply, ControlSocketError> {
319 let mut response_bytes = Vec::new();
320 response_frame
321 .encode(&mut response_bytes)
322 .map_err(ControlSocketError::EncodeFrame)?;
323 write_frame(writer, &response_bytes)?;
324 AdminReply::decode(response_frame.payload.as_slice())
325 .map_err(ControlSocketError::DecodeAdminReply)
326}
327
328#[derive(Debug, thiserror::Error)]
330pub enum ControlSocketError {
331 #[error(transparent)]
333 Connection(#[from] BrokerConnectionError),
334 #[error(transparent)]
336 Framing(#[from] FramingError),
337 #[error(transparent)]
339 AdminFrame(#[from] AdminFrameError),
340 #[error("failed to encode broker control response Frame: {0}")]
342 EncodeFrame(prost::EncodeError),
343 #[error("failed to decode admin reply payload: {0}")]
345 DecodeAdminReply(prost::DecodeError),
346}
347
348#[cfg(test)]
349mod cluster_g_tests {
350 use super::*;
351 use std::time::{Duration, Instant};
352
353 struct NeverReady;
354 impl Read for NeverReady {
355 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
356 Err(std::io::Error::new(
357 std::io::ErrorKind::WouldBlock,
358 "never ready",
359 ))
360 }
361 }
362 impl Write for NeverReady {
363 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
364 Ok(buf.len())
365 }
366 fn flush(&mut self) -> std::io::Result<()> {
367 Ok(())
368 }
369 }
370
371 #[test]
372 fn deadline_stream_read_times_out_on_silent_peer() {
373 let mut inner = NeverReady;
374 let mut ds = DeadlineStream::new(&mut inner, Instant::now() + Duration::from_millis(100));
375 let mut buf = [0u8; 4];
376 let start = Instant::now();
377 let err = ds.read(&mut buf).unwrap_err();
378 assert_eq!(err.kind(), std::io::ErrorKind::TimedOut);
379 assert!(start.elapsed() < Duration::from_secs(2), "must be bounded");
380 }
381
382 #[test]
383 fn deadline_stream_passes_ready_data_through() {
384 let data = b"hello";
385 let mut cursor = std::io::Cursor::new(data.to_vec());
386 let mut ds = DeadlineStream::new(&mut cursor, Instant::now() + Duration::from_secs(1));
387 let mut buf = [0u8; 5];
388 ds.read_exact(&mut buf).unwrap();
389 assert_eq!(&buf, data);
390 }
391}