1use std::collections::HashMap;
9use std::io::{self, Read, Write};
10use std::sync::Arc;
11use std::thread;
12
13use prost::Message;
14
15use crate::broker::protocol::{
16 hello_reply::Result as HelloReplyResult, read_frame_with_cap, write_frame, ErrorCode, Frame,
17 FrameKind, FramingError, HelloReply, PayloadEncoding, Refused, CONTROL_PAYLOAD_PROTOCOL,
18 MAX_HELLO_BYTES, PROTOCOL_VERSION,
19};
20use crate::broker::server::deadline_stream::{hello_read_deadline, with_nonblocking_deadline};
21use crate::broker::server::{HelloHandler, HelloRouter, PeerIdentity};
22use crate::platform::ipc;
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum PeerCredentialPolicy {
27 AllowAny,
29 OwnerOnly {
31 uid_or_sid: String,
33 },
34}
35
36impl PeerCredentialPolicy {
37 pub fn allow_any() -> Self {
39 Self::AllowAny
40 }
41
42 pub fn owner_only(uid_or_sid: impl Into<String>) -> Self {
44 Self::OwnerOnly {
45 uid_or_sid: uid_or_sid.into(),
46 }
47 }
48
49 pub fn current_user() -> Option<Self> {
51 ipc::current_user_id().ok().map(Self::owner_only)
52 }
53
54 pub fn allows(&self, peer: &PeerIdentity) -> bool {
56 match self {
57 Self::AllowAny => true,
58 Self::OwnerOnly { uid_or_sid } => {
59 !uid_or_sid.is_empty() && peer.uid_or_sid == *uid_or_sid
60 }
61 }
62 }
63}
64
65pub trait HelloResponder {
71 fn handle_frame(&self, frame: Frame, peer: PeerIdentity) -> HelloReply;
73}
74
75impl HelloResponder for HelloHandler {
76 fn handle_frame(&self, frame: Frame, peer: PeerIdentity) -> HelloReply {
77 Self::handle_frame(self, frame, peer)
78 }
79}
80
81impl HelloResponder for HelloRouter<'_> {
82 fn handle_frame(&self, frame: Frame, peer: PeerIdentity) -> HelloReply {
83 Self::handle_frame(self, frame, peer)
84 }
85}
86
87pub fn handle_hello_connection<S: Read + Write>(
93 stream: &mut S,
94 handler: &HelloHandler,
95 peer: PeerIdentity,
96) -> Result<HelloReply, BrokerConnectionError> {
97 handle_hello_connection_with(stream, handler, peer)
98}
99
100pub fn handle_hello_connection_with<S, R>(
105 stream: &mut S,
106 responder: &R,
107 peer: PeerIdentity,
108) -> Result<HelloReply, BrokerConnectionError>
109where
110 S: Read + Write,
111 R: HelloResponder + ?Sized,
112{
113 handle_hello_connection_with_peer_policy(
114 stream,
115 responder,
116 peer,
117 &PeerCredentialPolicy::allow_any(),
118 )
119 .map(|reply| reply.expect("allow-any policy must not drop peers"))
120}
121
122pub fn handle_hello_connection_with_peer_policy<S, R>(
128 stream: &mut S,
129 responder: &R,
130 peer: PeerIdentity,
131 peer_policy: &PeerCredentialPolicy,
132) -> Result<Option<HelloReply>, BrokerConnectionError>
133where
134 S: Read + Write,
135 R: HelloResponder + ?Sized,
136{
137 if !peer_policy.allows(&peer) {
138 return Ok(None);
139 }
140
141 let request_bytes = match read_frame_with_cap(stream, MAX_HELLO_BYTES) {
142 Ok(bytes) => bytes,
143 Err(err) => {
144 let reply = reply_for_framing_error(&err);
145 write_response_frame(stream, None, &reply)?;
146 return Ok(Some(reply));
147 }
148 };
149
150 let request_frame = match Frame::decode(request_bytes.as_slice()) {
151 Ok(frame) => frame,
152 Err(_) => {
153 let reply = refused_reply(ErrorCode::ErrorPeerRejected, "malformed broker Frame", 0);
154 write_response_frame(stream, None, &reply)?;
155 return Ok(Some(reply));
156 }
157 };
158
159 let reply = responder.handle_frame(request_frame.clone(), peer);
160 write_response_frame(stream, Some(&request_frame), &reply)?;
161 Ok(Some(reply))
162}
163
164pub fn serve_one_local_socket(
171 socket_path: &str,
172 handler: &HelloHandler,
173) -> Result<HelloReply, BrokerConnectionError> {
174 serve_one_local_socket_with(socket_path, handler)
175}
176
177pub fn serve_one_local_socket_with<R>(
180 socket_path: &str,
181 responder: &R,
182) -> Result<HelloReply, BrokerConnectionError>
183where
184 R: HelloResponder + ?Sized,
185{
186 serve_one_local_socket_with_peer_policy(
187 socket_path,
188 responder,
189 &PeerCredentialPolicy::allow_any(),
190 )
191 .map(|reply| reply.expect("allow-any policy must not drop peers"))
192}
193
194pub fn serve_one_local_socket_with_peer_policy<R>(
196 socket_path: &str,
197 responder: &R,
198 peer_policy: &PeerCredentialPolicy,
199) -> Result<Option<HelloReply>, BrokerConnectionError>
200where
201 R: HelloResponder + ?Sized,
202{
203 let listener = bind_local_socket(socket_path)?;
204 let cleanup = LocalSocketCleanup(socket_path);
205 let result = (|| {
206 let mut stream = listener.accept()?;
207 let peer = peer_identity_from_stream(&stream)?;
208 with_nonblocking_deadline(&mut stream, hello_read_deadline(), |stream| {
209 handle_hello_connection_with_peer_policy(stream, responder, peer, peer_policy)
210 })
211 })();
212 drop(listener);
213 drop(cleanup);
214 result
215}
216
217pub fn serve_local_socket_connections(
223 socket_path: &str,
224 handler: Arc<HelloHandler>,
225 connection_count: usize,
226) -> Result<(), BrokerConnectionError> {
227 serve_local_socket_connections_with_peer_policy(
228 socket_path,
229 handler,
230 connection_count,
231 &PeerCredentialPolicy::allow_any(),
232 )
233}
234
235pub fn serve_local_socket_connections_with_peer_policy(
237 socket_path: &str,
238 handler: Arc<HelloHandler>,
239 connection_count: usize,
240 peer_policy: &PeerCredentialPolicy,
241) -> Result<(), BrokerConnectionError> {
242 if connection_count == 0 {
243 return Ok(());
244 }
245
246 let listener = bind_local_socket(socket_path)?;
247 let cleanup = LocalSocketCleanup(socket_path);
248 let result = (|| {
249 let mut workers = Vec::with_capacity(connection_count);
250 let peer_policy = Arc::new(peer_policy.clone());
251
252 for _ in 0..connection_count {
253 let mut stream = listener.accept()?;
254 let peer = peer_identity_from_stream(&stream)?;
255 let handler = Arc::clone(&handler);
256 let peer_policy = Arc::clone(&peer_policy);
257 workers.push(thread::spawn(move || {
258 with_nonblocking_deadline(&mut stream, hello_read_deadline(), |stream| {
259 handle_hello_connection_with_peer_policy(
260 stream,
261 handler.as_ref(),
262 peer,
263 peer_policy.as_ref(),
264 )
265 .map(|_| ())
266 })
267 }));
268 }
269
270 for worker in workers {
271 match worker.join() {
272 Ok(Ok(())) => {}
273 Ok(Err(err)) => return Err(err),
274 Err(_) => return Err(BrokerConnectionError::WorkerPanic),
275 }
276 }
277 Ok(())
278 })();
279 drop(listener);
280 drop(cleanup);
281 result
282}
283
284pub fn serve_local_socket_connections_with<R>(
290 socket_path: &str,
291 responder: &R,
292 connection_count: usize,
293) -> Result<(), BrokerConnectionError>
294where
295 R: HelloResponder + ?Sized,
296{
297 serve_local_socket_connections_with_policy(
298 socket_path,
299 responder,
300 connection_count,
301 &PeerCredentialPolicy::allow_any(),
302 )
303}
304
305pub fn serve_local_socket_connections_with_policy<R>(
307 socket_path: &str,
308 responder: &R,
309 connection_count: usize,
310 peer_policy: &PeerCredentialPolicy,
311) -> Result<(), BrokerConnectionError>
312where
313 R: HelloResponder + ?Sized,
314{
315 if connection_count == 0 {
316 return Ok(());
317 }
318
319 let listener = bind_local_socket(socket_path)?;
320 let cleanup = LocalSocketCleanup(socket_path);
321 let result = (|| {
322 for _ in 0..connection_count {
323 let mut stream = listener.accept()?;
324 let peer = peer_identity_from_stream(&stream)?;
325 let _ = with_nonblocking_deadline(&mut stream, hello_read_deadline(), |stream| {
326 handle_hello_connection_with_peer_policy(stream, responder, peer, peer_policy)
327 })?;
328 }
329 Ok(())
330 })();
331 drop(listener);
332 drop(cleanup);
333 result
334}
335
336pub fn local_socket_name(socket_path: &str) -> io::Result<interprocess::local_socket::Name<'_>> {
339 super::singleton_bind::wrap_socket_name(socket_path).map_err(io::Error::other)
340}
341
342#[derive(Debug, thiserror::Error)]
344pub enum BrokerConnectionError {
345 #[error(transparent)]
347 Framing(#[from] FramingError),
348 #[error("failed to encode broker response Frame: {0}")]
350 EncodeFrame(prost::EncodeError),
351 #[error(transparent)]
353 Io(#[from] io::Error),
354 #[error("broker connection worker panicked")]
356 WorkerPanic,
357}
358
359pub(super) fn bind_local_socket(socket_path: &str) -> Result<ipc::Listener, BrokerConnectionError> {
360 let endpoint = ipc::Endpoint::new(socket_path.to_owned())?;
361 prepare_local_socket_path(&endpoint)?;
362 let listener = ipc::Listener::bind_owner_only(&endpoint)?;
363 Ok(listener)
364}
365
366pub(super) struct LocalSocketCleanup<'a>(pub(super) &'a str);
367
368impl Drop for LocalSocketCleanup<'_> {
369 fn drop(&mut self) {
370 cleanup_local_socket_path(self.0);
371 }
372}
373
374pub(super) fn write_response_frame<W: Write>(
375 writer: &mut W,
376 request_frame: Option<&Frame>,
377 reply: &HelloReply,
378) -> Result<(), BrokerConnectionError> {
379 let response_frame = Frame {
380 envelope_version: PROTOCOL_VERSION,
381 kind: FrameKind::Response as i32,
382 payload_protocol: CONTROL_PAYLOAD_PROTOCOL,
383 payload: reply.encode_to_vec(),
384 request_id: request_frame.map_or(0, |frame| frame.request_id),
385 payload_encoding: PayloadEncoding::None as i32,
386 deadline_unix_ms: 0,
387 traceparent: request_frame
388 .map(|frame| frame.traceparent.clone())
389 .unwrap_or_default(),
390 tracestate: request_frame
391 .map(|frame| frame.tracestate.clone())
392 .unwrap_or_default(),
393 };
394 let mut response_bytes = Vec::new();
395 response_frame
396 .encode(&mut response_bytes)
397 .map_err(BrokerConnectionError::EncodeFrame)?;
398 write_frame(writer, &response_bytes)?;
399 Ok(())
400}
401
402pub(super) fn reply_for_framing_error(error: &FramingError) -> HelloReply {
403 match error {
404 FramingError::UnsupportedFramingVersion { .. } => refused_reply(
405 ErrorCode::ErrorVersionUnsupported,
406 "unsupported framing version",
407 0,
408 ),
409 FramingError::FrameTooLarge { .. } => refused_reply(
410 ErrorCode::ErrorPeerRejected,
411 "initial Hello frame exceeds 64 KiB",
412 0,
413 ),
414 FramingError::UnexpectedEof { .. } | FramingError::Io(_) => {
415 refused_reply(ErrorCode::ErrorPeerRejected, "incomplete Hello frame", 0)
416 }
417 FramingError::Decode(_) => {
420 refused_reply(ErrorCode::ErrorPeerRejected, "malformed Hello frame", 0)
421 }
422 }
423}
424
425pub(super) fn refused_reply(
426 code: ErrorCode,
427 reason: impl Into<String>,
428 retry_after_ms: u64,
429) -> HelloReply {
430 HelloReply {
431 result: Some(HelloReplyResult::Refused(Refused {
432 reason: reason.into(),
433 daemon_min_protocol: PROTOCOL_VERSION,
434 daemon_max_protocol: PROTOCOL_VERSION,
435 code: code as i32,
436 details: HashMap::new(),
437 retry_after_ms,
438 })),
439 }
440}
441
442pub fn peer_identity_from_stream<S>(stream: &S) -> Result<PeerIdentity, BrokerConnectionError>
443where
444 S: ipc::PeerIdentitySource + ?Sized,
445{
446 peer_identity_from_source(stream)
447}
448
449#[cfg(feature = "client-async")]
457pub fn peer_identity_from_tokio_stream<S>(stream: &S) -> Result<PeerIdentity, BrokerConnectionError>
458where
459 S: ipc::PeerIdentitySource + ?Sized,
460{
461 peer_identity_from_source(stream)
462}
463
464fn peer_identity_from_source<S>(stream: &S) -> Result<PeerIdentity, BrokerConnectionError>
465where
466 S: ipc::PeerIdentitySource + ?Sized,
467{
468 let peer = ipc::PeerIdentitySource::ipc_peer_identity(stream)?;
469 Ok(PeerIdentity {
470 pid: peer.pid,
471 uid_or_sid: peer.user_id,
472 })
473}
474
475fn prepare_local_socket_path(endpoint: &ipc::Endpoint) -> io::Result<()> {
476 endpoint.ensure_owner_private_parent()?;
477 if endpoint.target_exists()? {
478 return Err(io::Error::new(
479 io::ErrorKind::AlreadyExists,
480 "broker local socket path already exists",
481 ));
482 }
483 Ok(())
484}
485
486fn cleanup_local_socket_path(socket_path: &str) {
487 if let Ok(endpoint) = ipc::Endpoint::new(socket_path.to_owned()) {
488 let _ = endpoint.retire();
489 }
490}
491
492#[cfg(test)]
493mod endpoint_tests {
494 use super::local_socket_name;
495
496 #[test]
497 fn resolved_pipe_has_one_canonical_interprocess_name() {
498 use crate::broker::server::singleton_bind::{resolve_socket_path, wrap_socket_name};
499
500 let resolved = resolve_socket_path(&format!(
501 "rpb-v2-canonical-name-test-{}",
502 std::process::id()
503 ))
504 .expect("resolve Windows pipe path");
505 let server = local_socket_name(&resolved).expect("server name");
506 let client = wrap_socket_name(&resolved).expect("client name");
507
508 assert_eq!(
509 server, client,
510 "server bind and client dial must normalize a resolved pipe identically"
511 );
512 }
513}