1pub mod commands;
12pub mod firewall_state;
13pub mod listening;
14pub mod protocol;
15pub mod queries;
16
17use crate::config::ControlConfig;
18use protocol::{Request, Response};
19use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
20use tokio::sync::{mpsc, oneshot};
21use tracing::{debug, info, warn};
22
23const MAX_REQUEST_SIZE: usize = 4096;
25
26const IO_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
28
29pub type ControlMessage = (Request, oneshot::Sender<Response>);
31
32#[derive(Clone)]
39pub(crate) struct ControlSenders {
40 query_tx: mpsc::Sender<ControlMessage>,
41 command_tx: mpsc::Sender<ControlMessage>,
42}
43
44impl ControlSenders {
45 pub(crate) fn new(
46 query_tx: mpsc::Sender<ControlMessage>,
47 command_tx: mpsc::Sender<ControlMessage>,
48 ) -> Self {
49 Self {
50 query_tx,
51 command_tx,
52 }
53 }
54
55 fn single(control_tx: mpsc::Sender<ControlMessage>) -> Self {
56 Self {
57 query_tx: control_tx.clone(),
58 command_tx: control_tx,
59 }
60 }
61
62 async fn send(
63 &self,
64 message: ControlMessage,
65 ) -> Result<(), mpsc::error::SendError<ControlMessage>> {
66 if message.0.command.starts_with("show_") {
67 self.query_tx.send(message).await
68 } else {
69 self.command_tx.send(message).await
70 }
71 }
72}
73
74async fn handle_connection_generic<S>(
79 stream: S,
80 control_senders: ControlSenders,
81) -> Result<(), Box<dyn std::error::Error>>
82where
83 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
84{
85 let (reader, mut writer) = tokio::io::split(stream);
86 let mut buf_reader = BufReader::new(reader);
87 let mut line = String::new();
88
89 let read_result = tokio::time::timeout(IO_TIMEOUT, async {
91 let mut total = 0usize;
92 loop {
93 let n = buf_reader.read_line(&mut line).await?;
94 if n == 0 {
95 break; }
97 total += n;
98 if total > MAX_REQUEST_SIZE {
99 return Err(std::io::Error::new(
100 std::io::ErrorKind::InvalidData,
101 "request too large",
102 ));
103 }
104 if line.ends_with('\n') {
105 break;
106 }
107 }
108 Ok(())
109 })
110 .await;
111
112 let response = match read_result {
113 Ok(Ok(())) if line.is_empty() => Response::error("empty request"),
114 Ok(Ok(())) => {
115 match serde_json::from_str::<Request>(line.trim()) {
117 Ok(request) => {
118 let (resp_tx, resp_rx) = oneshot::channel();
120 if control_senders.send((request, resp_tx)).await.is_err() {
121 Response::error("node shutting down")
122 } else {
123 match tokio::time::timeout(IO_TIMEOUT, resp_rx).await {
124 Ok(Ok(resp)) => resp,
125 Ok(Err(_)) => Response::error("response channel closed"),
126 Err(_) => Response::error("query timeout"),
127 }
128 }
129 }
130 Err(e) => Response::error(format!("invalid request: {}", e)),
131 }
132 }
133 Ok(Err(e)) => Response::error(format!("read error: {}", e)),
134 Err(_) => Response::error("read timeout"),
135 };
136
137 let json = serde_json::to_string(&response)?;
139 let write_result = tokio::time::timeout(IO_TIMEOUT, async {
140 writer.write_all(json.as_bytes()).await?;
141 writer.write_all(b"\n").await?;
142 writer.shutdown().await?;
143 Ok::<_, std::io::Error>(())
144 })
145 .await;
146
147 if let Err(_) | Ok(Err(_)) = write_result {
148 debug!("Control socket write failed or timed out");
149 }
150
151 Ok(())
152}
153
154#[cfg(unix)]
159mod unix_impl {
160 use super::*;
161 use std::path::{Path, PathBuf};
162 use tokio::net::UnixListener;
163
164 pub struct ControlSocket {
168 listener: UnixListener,
169 socket_path: PathBuf,
170 }
171
172 impl ControlSocket {
173 pub fn bind(config: &ControlConfig) -> Result<Self, std::io::Error> {
178 let socket_path = PathBuf::from(&config.socket_path);
179
180 if let Some(parent) = socket_path.parent()
182 && !parent.exists()
183 {
184 std::fs::create_dir_all(parent)?;
185 debug!(path = %parent.display(), "Created control socket directory");
186 }
187
188 if socket_path.exists() {
190 Self::remove_stale_socket(&socket_path)?;
191 }
192
193 let listener = UnixListener::bind(&socket_path)?;
194
195 use std::os::unix::fs::PermissionsExt;
198 std::fs::set_permissions(&socket_path, std::fs::Permissions::from_mode(0o770))?;
199 Self::chown_to_fips_group(&socket_path);
200 if let Some(parent) = socket_path.parent() {
201 Self::chown_to_fips_group(parent);
202 }
203
204 info!(path = %socket_path.display(), "Control socket listening");
205
206 Ok(Self {
207 listener,
208 socket_path,
209 })
210 }
211
212 fn remove_stale_socket(path: &Path) -> Result<(), std::io::Error> {
217 match std::os::unix::net::UnixStream::connect(path) {
219 Ok(_) => {
220 Err(std::io::Error::new(
222 std::io::ErrorKind::AddrInUse,
223 format!("control socket already in use: {}", path.display()),
224 ))
225 }
226 Err(_) => {
227 debug!(path = %path.display(), "Removing stale control socket");
229 std::fs::remove_file(path)?;
230 Ok(())
231 }
232 }
233 }
234
235 fn chown_to_fips_group(path: &Path) {
237 use std::ffi::CString;
238 use std::os::unix::ffi::OsStrExt;
239
240 let group_name = CString::new("fips").unwrap();
242 let grp = unsafe { libc::getgrnam(group_name.as_ptr()) };
243 if grp.is_null() {
244 debug!(
245 "'fips' group not found, skipping chown for {}",
246 path.display()
247 );
248 return;
249 }
250 let gid = unsafe { (*grp).gr_gid };
251
252 let c_path = match CString::new(path.as_os_str().as_bytes()) {
253 Ok(p) => p,
254 Err(_) => return,
255 };
256 let ret = unsafe { libc::chown(c_path.as_ptr(), u32::MAX, gid) };
257 if ret != 0 {
258 warn!(
259 path = %path.display(),
260 error = %std::io::Error::last_os_error(),
261 "Failed to chown control socket to 'fips' group"
262 );
263 }
264 }
265
266 pub async fn accept_loop(self, control_tx: mpsc::Sender<ControlMessage>) {
275 self.accept_loop_split(ControlSenders::single(control_tx))
276 .await;
277 }
278
279 pub(crate) async fn accept_loop_split(self, control_senders: ControlSenders) {
280 loop {
281 let (stream, _addr) = match self.listener.accept().await {
282 Ok(conn) => conn,
283 Err(e) => {
284 warn!(error = %e, "Control socket accept failed");
285 continue;
286 }
287 };
288
289 let senders = control_senders.clone();
290 tokio::spawn(async move {
291 if let Err(e) = handle_connection_generic(stream, senders).await {
292 debug!(error = %e, "Control connection error");
293 }
294 });
295 }
296 }
297
298 pub fn socket_path(&self) -> &Path {
300 &self.socket_path
301 }
302
303 fn cleanup(&self) {
305 if self.socket_path.exists() {
306 if let Err(e) = std::fs::remove_file(&self.socket_path) {
307 warn!(
308 path = %self.socket_path.display(),
309 error = %e,
310 "Failed to remove control socket"
311 );
312 } else {
313 debug!(path = %self.socket_path.display(), "Control socket removed");
314 }
315 }
316 }
317 }
318
319 impl Drop for ControlSocket {
320 fn drop(&mut self) {
321 self.cleanup();
322 }
323 }
324}
325
326#[cfg(windows)]
331mod windows_impl {
332 use super::*;
333 use tokio::net::TcpListener;
334
335 const DEFAULT_CONTROL_PORT: u16 = 21210;
337
338 pub struct ControlSocket {
348 listener: TcpListener,
349 port: u16,
350 }
351
352 impl ControlSocket {
353 pub fn bind(config: &ControlConfig) -> Result<Self, std::io::Error> {
359 let port: u16 = match config.socket_path.parse() {
360 Ok(p) => p,
361 Err(e) => {
362 warn!(
363 path = %config.socket_path,
364 error = %e,
365 default = DEFAULT_CONTROL_PORT,
366 "Invalid control port, using default"
367 );
368 DEFAULT_CONTROL_PORT
369 }
370 };
371
372 let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
373 let std_listener = std::net::TcpListener::bind(addr)?;
374 std_listener.set_nonblocking(true)?;
375 let listener = TcpListener::from_std(std_listener)?;
376
377 info!(port = port, "Control socket listening on localhost");
378
379 Ok(Self { listener, port })
380 }
381
382 pub fn port(&self) -> u16 {
384 self.port
385 }
386
387 pub async fn accept_loop(self, control_tx: mpsc::Sender<ControlMessage>) {
392 self.accept_loop_split(ControlSenders::single(control_tx))
393 .await;
394 }
395
396 pub(crate) async fn accept_loop_split(self, control_senders: ControlSenders) {
397 loop {
398 let (stream, addr) = match self.listener.accept().await {
399 Ok(conn) => conn,
400 Err(e) => {
401 warn!(error = %e, "Control socket accept failed");
402 continue;
403 }
404 };
405
406 if !addr.ip().is_loopback() {
408 warn!(addr = %addr, "Rejected non-localhost control connection");
409 continue;
410 }
411
412 let senders = control_senders.clone();
413 tokio::spawn(async move {
414 if let Err(e) = handle_connection_generic(stream, senders).await {
415 debug!(error = %e, "Control connection error");
416 }
417 });
418 }
419 }
420 }
421}
422
423#[cfg(unix)]
425pub use unix_impl::ControlSocket;
426#[cfg(windows)]
427pub use windows_impl::ControlSocket;
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432
433 #[tokio::test]
434 async fn control_senders_route_show_queries_to_query_lane() {
435 let (query_tx, mut query_rx) = mpsc::channel(1);
436 let (command_tx, mut command_rx) = mpsc::channel(1);
437 let senders = ControlSenders::new(query_tx, command_tx);
438 let (resp_tx, _resp_rx) = oneshot::channel();
439
440 senders
441 .send((
442 Request {
443 command: "show_status".to_string(),
444 params: None,
445 },
446 resp_tx,
447 ))
448 .await
449 .unwrap();
450
451 let (request, _response_tx) = query_rx.recv().await.unwrap();
452 assert_eq!(request.command, "show_status");
453 assert!(command_rx.try_recv().is_err());
454 }
455
456 #[tokio::test]
457 async fn control_senders_route_mutations_to_command_lane() {
458 let (query_tx, mut query_rx) = mpsc::channel(1);
459 let (command_tx, mut command_rx) = mpsc::channel(1);
460 let senders = ControlSenders::new(query_tx, command_tx);
461 let (resp_tx, _resp_rx) = oneshot::channel();
462
463 senders
464 .send((
465 Request {
466 command: "connect".to_string(),
467 params: None,
468 },
469 resp_tx,
470 ))
471 .await
472 .unwrap();
473
474 let (request, _response_tx) = command_rx.recv().await.unwrap();
475 assert_eq!(request.command, "connect");
476 assert!(query_rx.try_recv().is_err());
477 }
478
479 #[cfg(windows)]
480 #[tokio::test]
481 async fn test_tcp_control_socket_bind() {
482 let config = ControlConfig {
483 enabled: true,
484 socket_path: "0".to_string(), };
486
487 let _socket = ControlSocket::bind(&config).expect("failed to bind control socket");
489 }
490
491 #[cfg(windows)]
492 #[tokio::test]
493 async fn test_tcp_control_socket_invalid_port_uses_default() {
494 let config = ControlConfig {
495 enabled: true,
496 socket_path: "not-a-port".to_string(),
497 };
498
499 let result = ControlSocket::bind(&config);
502 if let Ok(socket) = result {
504 assert_eq!(socket.port(), 21210);
505 }
506 }
507}