brokerd: state.sock answers memoryd's question about a session's state
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -25,12 +25,14 @@ impl Default for Paths {
|
||||
}
|
||||
}
|
||||
|
||||
/// An empty path means "the default under `home`".
|
||||
/// The sockets `brokerd` serves. An empty path means "the default under `home`".
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields, default)]
|
||||
pub struct Sockets {
|
||||
pub broker: PathBuf,
|
||||
pub admin: PathBuf,
|
||||
/// `memoryd`'s session-state questions (M5c spec, section 7).
|
||||
pub state: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
@@ -284,6 +286,14 @@ impl Config {
|
||||
self.sockets.admin.clone()
|
||||
}
|
||||
}
|
||||
/// `state.sock`, where `memoryd` asks for a session's state.
|
||||
pub fn state_socket(&self) -> PathBuf {
|
||||
if self.sockets.state.as_os_str().is_empty() {
|
||||
self.paths.home.join("run/memory-broker/state.sock")
|
||||
} else {
|
||||
self.sockets.state.clone()
|
||||
}
|
||||
}
|
||||
/// `gateway.sock`, when `[gateway]` is set.
|
||||
pub fn gateway_socket(&self) -> Option<PathBuf> {
|
||||
let gateway = self.gateway.as_ref()?;
|
||||
|
||||
@@ -18,3 +18,4 @@ pub mod policy;
|
||||
pub mod runner;
|
||||
pub mod serve;
|
||||
pub mod state;
|
||||
pub mod state_socket;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! `brokerd`: the broker role. `serve` takes the audit lock, binds both sockets, and serves.
|
||||
//! `brokerd`: the broker role. `serve` takes the audit lock, binds its three sockets, and serves.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
@@ -58,6 +58,7 @@ fn main() -> ExitCode {
|
||||
// Keep the socket paths for the serving notice, before `cfg` is moved into `start`.
|
||||
let broker_path = cfg.broker_socket();
|
||||
let admin_path = cfg.admin_socket();
|
||||
let state_path = cfg.state_socket();
|
||||
|
||||
// The runtime and the line that says which. A `[runner]` runs calls in containers; without it,
|
||||
// every call is refused, as in M3a.
|
||||
@@ -117,6 +118,7 @@ fn main() -> ExitCode {
|
||||
broker_path.display(),
|
||||
admin_path.display()
|
||||
);
|
||||
eprintln!("brokerd: serving session state on {}", state_path.display());
|
||||
eprintln!("brokerd: {runtime_notice}");
|
||||
|
||||
match started.run() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! `brokerd serve`: bring the broker up, listen on both sockets, and keep it running.
|
||||
//! `brokerd serve`: bring the broker up, listen on its three sockets, and keep it running.
|
||||
|
||||
use std::fs::{DirBuilder, Permissions};
|
||||
use std::io;
|
||||
@@ -16,6 +16,7 @@ use crate::config::Config;
|
||||
use crate::ledger::Ledger;
|
||||
use crate::runner::Runtime;
|
||||
use crate::state::StateStore;
|
||||
use crate::state_socket;
|
||||
use bxcore::{ChainFailure, Timestamp};
|
||||
|
||||
/// A failure while `brokerd serve` is starting up.
|
||||
@@ -48,16 +49,17 @@ impl std::error::Error for ServeError {
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of `start`: a running broker with both sockets open.
|
||||
/// The result of `start`: a running broker with its three sockets open.
|
||||
pub struct Started {
|
||||
pub broker: Arc<Broker>,
|
||||
pub recovered: bool,
|
||||
pub accepted: Option<ChainFailure>,
|
||||
tools: UnixListener,
|
||||
admin: UnixListener,
|
||||
state: UnixListener,
|
||||
}
|
||||
|
||||
/// Start the broker: open the audit chain, bind both sockets, and build the ledger and broker.
|
||||
/// Start the broker: open the audit chain, bind the three sockets, and build the ledger and broker.
|
||||
pub fn start(
|
||||
cfg: Config,
|
||||
accept_break: bool,
|
||||
@@ -67,7 +69,9 @@ pub fn start(
|
||||
// The audit lock comes first: if the chain will not open, no directory or socket is made.
|
||||
let opened = Writer::open(&cfg.audit_dir(), accept_break).map_err(ServeError::Audit)?;
|
||||
|
||||
// Both sockets, in the order the task gives.
|
||||
// The three sockets. `state.sock` comes first: whoever waits for the tool and admin sockets
|
||||
// then finds it bound too.
|
||||
let state = listen(&cfg.state_socket())?;
|
||||
let tools = listen(&cfg.broker_socket())?;
|
||||
let admin = listen(&cfg.admin_socket())?;
|
||||
|
||||
@@ -93,6 +97,7 @@ pub fn start(
|
||||
accepted: opened.accepted.map(|b| *b),
|
||||
tools,
|
||||
admin,
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -156,6 +161,7 @@ fn listen(socket: &Path) -> Result<UnixListener, ServeError> {
|
||||
enum Which {
|
||||
Tools,
|
||||
Admin,
|
||||
State,
|
||||
}
|
||||
|
||||
/// Run the broker: start the expiry thread, one accept thread per socket, then wait for the
|
||||
@@ -166,6 +172,7 @@ impl Started {
|
||||
broker,
|
||||
tools,
|
||||
admin,
|
||||
state,
|
||||
..
|
||||
} = self;
|
||||
|
||||
@@ -187,7 +194,8 @@ impl Started {
|
||||
let (tx, rx) = mpsc::channel::<io::Error>();
|
||||
|
||||
accept_loop(tools, Arc::clone(&broker), tx.clone(), Which::Tools)?;
|
||||
accept_loop(admin, Arc::clone(&broker), tx, Which::Admin)?;
|
||||
accept_loop(admin, Arc::clone(&broker), tx.clone(), Which::Admin)?;
|
||||
accept_loop(state, Arc::clone(&broker), tx, Which::State)?;
|
||||
|
||||
// The first listener error stops the daemon.
|
||||
match rx.recv() {
|
||||
@@ -200,7 +208,7 @@ impl Started {
|
||||
/// Accept streams from one listener forever, starting a handler thread per stream. When the
|
||||
/// system is out of file descriptors or memory, the listener pauses and tries again instead of
|
||||
/// stopping: otherwise a peer holding many idle connections could stop the daemon, and the other
|
||||
/// socket with it. Any other error stops the daemon through `tx`.
|
||||
/// sockets with it. Any other error stops the daemon through `tx`.
|
||||
fn accept_loop(
|
||||
listener: UnixListener,
|
||||
broker: Arc<Broker>,
|
||||
@@ -210,6 +218,7 @@ fn accept_loop(
|
||||
let name = match which {
|
||||
Which::Tools => "accept-broker",
|
||||
Which::Admin => "accept-admin",
|
||||
Which::State => "accept-state",
|
||||
};
|
||||
std::thread::Builder::new()
|
||||
.name(name.to_string())
|
||||
@@ -248,6 +257,7 @@ fn serve_one(stream: UnixStream, broker: &Arc<Broker>, which: Which) {
|
||||
let started = std::thread::Builder::new().spawn(move || match which {
|
||||
Which::Tools => broker::handle(stream, &for_thread),
|
||||
Which::Admin => admin::handle(stream, &for_thread),
|
||||
Which::State => state_socket::handle(stream, &for_thread),
|
||||
});
|
||||
if let Err(e) = started {
|
||||
broker.log(&format!(
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
//! One `state.sock` connection: `memoryd` asks for a session's state when a turn ends, and the
|
||||
//! answer is what the state store holds (M5c spec, section 7). It reads, and never changes
|
||||
//! anything: no state, no audit record.
|
||||
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
use bxcore::{ErrorCode, Message, SessionStateReply, WireError};
|
||||
|
||||
use crate::broker::{Broker, forbid, read_request, send};
|
||||
use crate::state::StateStore;
|
||||
|
||||
/// The socket's name in the lines `brokerd` logs and the errors it sends.
|
||||
pub const SOCKET: &str = "state.sock";
|
||||
|
||||
/// Serve one `state.sock` connection from `memoryd`: one question, one final answer, then the
|
||||
/// connection is closed.
|
||||
pub fn handle(mut stream: UnixStream, broker: &Broker) {
|
||||
// 1. The peer is gone before it speaks, or its frame is malformed: answered, then return.
|
||||
let Some(envelope) = read_request(&mut stream) else {
|
||||
return;
|
||||
};
|
||||
let id = envelope.id;
|
||||
let msg = envelope.msg;
|
||||
|
||||
// 2. Only the question belongs here. `bxcore::SessionState` is matched through `Message`, so
|
||||
// this file names neither it nor `policy::SessionState`, which the store returns.
|
||||
let Message::SessionState(question) = msg else {
|
||||
forbid(broker, &mut stream, id, &msg, SOCKET);
|
||||
return;
|
||||
};
|
||||
|
||||
// 3. The store the ledger writes. A session without a file is the store's default, `private`
|
||||
// and trusted; a file that cannot be read is an error, never that default.
|
||||
let store = StateStore::new(&broker.cfg().state_dir());
|
||||
let response = match store.read(&question.session) {
|
||||
Ok(state) => Message::SessionStateReply(SessionStateReply {
|
||||
class: state.taint,
|
||||
untrusted: state.untrusted,
|
||||
}),
|
||||
Err(e) => {
|
||||
broker.log(&format!("brokerd: {e}"));
|
||||
Message::Error(WireError {
|
||||
code: ErrorCode::Internal,
|
||||
detail: e.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
// 4. The final frame; a failed send is ignored, as the peer is gone.
|
||||
let _ = send(&mut stream, id, true, response);
|
||||
}
|
||||
Reference in New Issue
Block a user