use std::collections::HashMap;
#[cfg(feature = "rpc-tls")]
use std::net::{SocketAddr, TcpListener, TcpStream};
#[cfg(target_os = "android")]
use std::os::android::net::SocketAddrExt;
#[cfg(target_os = "linux")]
use std::os::linux::net::SocketAddrExt;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::net::SocketAddr as UnixSocketAddr;
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use crate::binder::{Interface, Remotable, SIBinder, TransactionCode};
use crate::error::{Result, StatusCode};
use crate::native::Binder;
use crate::parcel::Parcel;
use super::deadline::PhaseDeadline;
use super::session::{RpcSession, RpcSessionId, RpcSessionInner};
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
use super::transport::VsockTransport;
use super::transport::{PeerIdentity, RpcTransport, UnixTransport};
#[cfg(feature = "rpc-tls")]
use super::transport::{TlsStream, TlsTransport};
use super::wire_android13::BAD_SESSION_ID_SIZE;
use super::RpcResult;
#[cfg(feature = "rpc-tls")]
type TlsServerConfigCell = Mutex<Option<Arc<rustls::ServerConfig>>>;
enum ServerListener {
Unix(UnixListener),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
Vsock(vsock::VsockListener),
#[cfg(feature = "rpc-tls")]
Tcp(TcpListener),
}
enum BindAddress {
Unix {
path: PathBuf,
file: Option<(u64, u64)>,
},
#[cfg(any(target_os = "linux", target_os = "android"))]
UnixAbstract,
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
Vsock { cid: u32, port: u32 },
#[cfg(feature = "rpc-tls")]
Tcp(SocketAddr),
}
enum RawAccepted {
Unix(UnixStream),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
Vsock(vsock::VsockStream),
#[cfg(feature = "rpc-tls")]
Tcp(TcpStream),
}
impl ServerListener {
fn set_nonblocking(&self, on: bool) -> std::io::Result<()> {
match self {
ServerListener::Unix(l) => l.set_nonblocking(on),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
ServerListener::Vsock(l) => l.set_nonblocking(on),
#[cfg(feature = "rpc-tls")]
ServerListener::Tcp(l) => l.set_nonblocking(on),
}
}
fn accept_raw(&self) -> std::io::Result<RawAccepted> {
match self {
ServerListener::Unix(l) => {
let (stream, _addr) = l.accept()?;
Ok(RawAccepted::Unix(stream))
}
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
ServerListener::Vsock(l) => {
let (stream, _addr) = l.accept()?;
Ok(RawAccepted::Vsock(stream))
}
#[cfg(feature = "rpc-tls")]
ServerListener::Tcp(l) => {
let (stream, _addr) = l.accept()?;
Ok(RawAccepted::Tcp(stream))
}
}
}
}
impl RawAccepted {
fn prepare_for_worker(&self) -> std::io::Result<()> {
match self {
RawAccepted::Unix(s) => s.set_nonblocking(false),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
RawAccepted::Vsock(s) => s.set_nonblocking(false),
#[cfg(feature = "rpc-tls")]
RawAccepted::Tcp(s) => {
s.set_nonblocking(false)?;
s.set_nodelay(true)
}
}
}
fn set_read_timeout(&self, timeout: Option<std::time::Duration>) -> std::io::Result<()> {
match self {
RawAccepted::Unix(s) => s.set_read_timeout(timeout),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
RawAccepted::Vsock(s) => s.set_read_timeout(timeout),
#[cfg(feature = "rpc-tls")]
RawAccepted::Tcp(s) => s.set_read_timeout(timeout),
}
}
fn set_write_timeout(&self, timeout: Option<std::time::Duration>) -> std::io::Result<()> {
match self {
RawAccepted::Unix(s) => s.set_write_timeout(timeout),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
RawAccepted::Vsock(s) => s.set_write_timeout(timeout),
#[cfg(feature = "rpc-tls")]
RawAccepted::Tcp(s) => s.set_write_timeout(timeout),
}
}
fn shutdown_handle(&self) -> Option<super::deadline::Cut> {
use std::os::fd::AsFd;
let sock = match self {
RawAccepted::Unix(s) => s.as_fd(),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
RawAccepted::Vsock(s) => s.as_fd(),
#[cfg(feature = "rpc-tls")]
RawAccepted::Tcp(s) => s.as_fd(),
};
super::transport::socket_shutdown_handle(sock)
}
#[cfg(feature = "rpc-tls")]
fn into_transport(
self,
tls_config: Option<Arc<rustls::ServerConfig>>,
) -> RpcResult<Box<dyn RpcTransport>> {
if let Some(cfg) = tls_config {
#[cfg(target_vendor = "apple")]
{
use std::os::fd::AsFd;
let fd = match &self {
RawAccepted::Unix(s) => s.as_fd(),
RawAccepted::Tcp(s) => s.as_fd(),
};
rustix::net::sockopt::set_socket_nosigpipe(fd, true)
.map_err(std::io::Error::from)?;
}
let stream: Box<dyn TlsStream> = match self {
RawAccepted::Unix(s) => Box::new(s),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
RawAccepted::Vsock(s) => Box::new(s),
RawAccepted::Tcp(s) => Box::new(s),
};
return Ok(Box::new(TlsTransport::accept_stream(stream, cfg)?));
}
self.into_native_transport()
}
#[cfg(not(feature = "rpc-tls"))]
fn into_transport(self) -> RpcResult<Box<dyn RpcTransport>> {
self.into_native_transport()
}
fn into_native_transport(self) -> RpcResult<Box<dyn RpcTransport>> {
match self {
RawAccepted::Unix(s) => Ok(Box::new(UnixTransport::from_stream(s)?)),
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
RawAccepted::Vsock(s) => Ok(Box::new(VsockTransport::from_stream(s)?)),
#[cfg(feature = "rpc-tls")]
RawAccepted::Tcp(_) => Err(super::RpcError::Protocol(
"plain-text TCP server is not exposed (TLS-only on TCP)",
)),
}
}
}
const DIRECTORY_DESC: &str = "rsbinder.rpc.IServiceDirectory";
const TX_GET_SERVICE: TransactionCode = crate::binder::FIRST_CALL_TRANSACTION;
const DEFAULT_HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
struct ServiceDirectory {
services: Arc<Mutex<HashMap<String, SIBinder>>>,
}
impl Remotable for ServiceDirectory {
fn descriptor() -> &'static str {
DIRECTORY_DESC
}
fn on_transact(
&self,
code: TransactionCode,
reader: &mut Parcel,
reply: &mut Parcel,
) -> Result<()> {
match code {
TX_GET_SERVICE => {
let name: String = reader.read()?;
let found = self
.services
.lock()
.expect("named poisoned")
.get(&name)
.cloned();
match found {
Some(b) => {
reply.write(&crate::Status::from(StatusCode::Ok))?;
reply.write(&b)
}
None => reply.write(&crate::Status::from(StatusCode::NameNotFound)),
}
}
_ => Err(StatusCode::UnknownTransaction),
}
}
fn on_dump(&self, _w: &mut dyn std::io::Write, _a: &[String]) -> Result<()> {
Ok(())
}
}
type Authorizer = Arc<dyn Fn(&PeerIdentity) -> bool + Send + Sync>;
struct R34Session {
inner: std::sync::Weak<RpcSessionInner>,
founder: PeerIdentity,
}
pub struct RpcServer {
listener: ServerListener,
bind: BindAddress,
#[cfg(feature = "rpc-tls")]
tls_config: TlsServerConfigCell,
root: Mutex<Option<SIBinder>>,
named: Arc<Mutex<HashMap<String, SIBinder>>>,
directory: SIBinder,
max_threads: Mutex<u32>,
fd_unix_supported: AtomicBool,
wire_max_version: Mutex<Option<u32>>,
max_connections: Mutex<Option<usize>>,
handshake_timeout: Mutex<Option<std::time::Duration>>,
idle_timeout: Mutex<Option<std::time::Duration>>,
reply_timeout: Mutex<Option<std::time::Duration>>,
authorizer: Mutex<Option<Authorizer>>,
#[doc(hidden)]
attach_shutdown_probe: Mutex<Option<Arc<dyn Fn() + Send + Sync>>>,
sessions: Mutex<HashMap<RpcSessionId, std::sync::Weak<RpcSessionInner>>>,
r34_sessions: Mutex<HashMap<i32, R34Session>>,
session_registered: AtomicUsize,
attached_count: AtomicUsize,
rejected_unknown_id: AtomicUsize,
shutdown: Arc<AtomicBool>,
terminating: Arc<AtomicBool>,
workers: Mutex<Vec<JoinHandle<()>>>,
live_sessions: Mutex<Vec<std::sync::Weak<RpcSessionInner>>>,
}
fn remove_stale_socket(path: &Path) -> Result<()> {
use std::io::ErrorKind;
use std::os::unix::fs::FileTypeExt;
let meta = match std::fs::symlink_metadata(path) {
Ok(meta) => meta,
Err(e) if e.kind() == ErrorKind::NotFound => return Ok(()),
Err(e) => return Err(e.into()),
};
if !meta.file_type().is_socket() {
log::error!(
"RpcServer::setup_unix_server: {path:?} exists and is not a socket; not removing it"
);
return Err(StatusCode::AlreadyExists);
}
remove_if_stale(path)
}
#[cfg(target_vendor = "apple")]
fn remove_if_stale(path: &Path) -> Result<()> {
log::error!(
"RpcServer::setup_unix_server: a socket exists at {path:?}; Apple platforms never \
remove it (delete it once no server listens on it)"
);
Err(StatusCode::from(rustix::io::Errno::ADDRINUSE))
}
#[cfg(not(target_vendor = "apple"))]
fn remove_if_stale(path: &Path) -> Result<()> {
use rustix::io::Errno;
use rustix::net::{AddressFamily, SocketAddrUnix, SocketFlags, SocketType};
use std::io::ErrorKind;
let flags = SocketFlags::CLOEXEC | SocketFlags::NONBLOCK;
let probe = rustix::net::socket_with(AddressFamily::UNIX, SocketType::STREAM, flags, None)
.map_err(std::io::Error::from)?;
let addr = SocketAddrUnix::new(path).map_err(std::io::Error::from)?;
match rustix::net::connect(&probe, &addr) {
Ok(()) | Err(Errno::AGAIN) | Err(Errno::INPROGRESS) => {
log::error!("RpcServer::setup_unix_server: another server is listening on {path:?}");
Err(StatusCode::from(Errno::ADDRINUSE))
}
Err(Errno::CONNREFUSED) => std::fs::remove_file(path).or_else(|e| match e.kind() {
ErrorKind::NotFound => Ok(()),
_ => Err(StatusCode::from(e)),
}),
Err(e) => {
log::error!("RpcServer::setup_unix_server: cannot probe {path:?}: {e}");
Err(std::io::Error::from(e).into())
}
}
}
fn socket_file_id(path: &Path) -> Option<(u64, u64)> {
use std::os::unix::fs::MetadataExt;
std::fs::symlink_metadata(path)
.ok()
.map(|meta| (meta.dev(), meta.ino()))
}
impl RpcServer {
pub fn setup_unix_server(path: impl Into<PathBuf>) -> Result<Arc<RpcServer>> {
let path = path.into();
remove_stale_socket(&path)?;
let listener = UnixListener::bind(&path)?;
let file = socket_file_id(&path);
let listener = ServerListener::Unix(listener);
listener.set_nonblocking(true)?;
Ok(Self::wrap(listener, BindAddress::Unix { path, file }))
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn setup_unix_server_abstract(name: &[u8]) -> Result<Arc<RpcServer>> {
let addr = UnixSocketAddr::from_abstract_name(name)?;
let listener = ServerListener::Unix(UnixListener::bind_addr(&addr)?);
listener.set_nonblocking(true)?;
Ok(Self::wrap(listener, BindAddress::UnixAbstract))
}
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
pub fn setup_vsock_server(cid: u32, port: u32) -> Result<Arc<RpcServer>> {
let listener = vsock::VsockListener::bind_with_cid_port(cid, port).map_err(|e| {
log::warn!("VsockListener::bind_with_cid_port({cid}, {port}) failed: {e}");
crate::StatusCode::from(e)
})?;
let listener = ServerListener::Vsock(listener);
listener.set_nonblocking(true)?;
Ok(Self::wrap(listener, BindAddress::Vsock { cid, port }))
}
fn wrap(listener: ServerListener, bind: BindAddress) -> Arc<RpcServer> {
let named: Arc<Mutex<HashMap<String, SIBinder>>> = Arc::new(Mutex::new(HashMap::new()));
let directory = Interface::as_binder(&Binder::new(ServiceDirectory {
services: Arc::clone(&named),
}));
Arc::new(RpcServer {
listener,
bind,
#[cfg(feature = "rpc-tls")]
tls_config: Mutex::new(None),
root: Mutex::new(None),
named,
directory,
max_threads: Mutex::new(1),
fd_unix_supported: AtomicBool::new(false),
wire_max_version: Mutex::new(None),
max_connections: Mutex::new(None),
handshake_timeout: Mutex::new(Some(DEFAULT_HANDSHAKE_TIMEOUT)),
idle_timeout: Mutex::new(None),
reply_timeout: Mutex::new(None),
authorizer: Mutex::new(None),
attach_shutdown_probe: Mutex::new(None),
sessions: Mutex::new(HashMap::new()),
r34_sessions: Mutex::new(HashMap::new()),
session_registered: AtomicUsize::new(0),
attached_count: AtomicUsize::new(0),
rejected_unknown_id: AtomicUsize::new(0),
shutdown: Arc::new(AtomicBool::new(false)),
terminating: Arc::new(AtomicBool::new(false)),
workers: Mutex::new(Vec::new()),
live_sessions: Mutex::new(Vec::new()),
})
}
#[cfg(feature = "rpc-tls")]
pub fn setup_unix_server_tls(
path: impl Into<PathBuf>,
config: Arc<rustls::ServerConfig>,
) -> Result<Arc<RpcServer>> {
let server = Self::setup_unix_server(path)?;
*server.tls_config.lock().expect("tls_config poisoned") = Some(config);
Ok(server)
}
#[cfg(feature = "rpc-tls")]
pub fn setup_tcp_server_tls(
addr: impl std::net::ToSocketAddrs,
config: Arc<rustls::ServerConfig>,
) -> Result<Arc<RpcServer>> {
let listener = TcpListener::bind(addr)?;
let local = listener.local_addr()?;
let listener = ServerListener::Tcp(listener);
listener.set_nonblocking(true)?;
let server = Self::wrap(listener, BindAddress::Tcp(local));
*server.tls_config.lock().expect("tls_config poisoned") = Some(config);
Ok(server)
}
#[cfg(all(
feature = "rpc-tls",
feature = "rpc-vsock",
any(target_os = "linux", target_os = "android")
))]
pub fn setup_vsock_server_tls(
cid: u32,
port: u32,
config: Arc<rustls::ServerConfig>,
) -> Result<Arc<RpcServer>> {
let server = Self::setup_vsock_server(cid, port)?;
*server.tls_config.lock().expect("tls_config poisoned") = Some(config);
Ok(server)
}
#[cfg(feature = "rpc-tls")]
fn tls_snapshot(&self) -> Option<Arc<rustls::ServerConfig>> {
self.tls_config.lock().expect("tls_config poisoned").clone()
}
pub fn set_root(&self, binder: SIBinder) -> Result<()> {
super::refuse_remote(&binder, "RpcServer::set_root")?;
*self.root.lock().expect("root poisoned") = Some(binder);
Ok(())
}
pub fn add_service(&self, name: &str, binder: SIBinder) -> Result<()> {
super::refuse_remote(&binder, "RpcServer::add_service")?;
self.named
.lock()
.expect("named poisoned")
.insert(name.to_string(), binder);
*self.root.lock().expect("root poisoned") = Some(self.directory.clone());
Ok(())
}
pub fn set_max_threads(&self, n: u32) {
*self.max_threads.lock().expect("max_threads poisoned") = n.max(1);
}
pub fn set_max_connections(&self, n: usize) {
*self
.max_connections
.lock()
.expect("max_connections poisoned") = Some(n.max(1));
}
pub fn set_handshake_timeout(&self, timeout: Option<std::time::Duration>) {
let timeout = match timeout {
Some(d) if d.is_zero() => {
log::error!(
"RpcServer::set_handshake_timeout: a zero duration is not a \
valid deadline; keeping the default"
);
Some(DEFAULT_HANDSHAKE_TIMEOUT)
}
other => other,
};
*self
.handshake_timeout
.lock()
.expect("handshake_timeout poisoned") = timeout;
}
pub fn set_idle_timeout(&self, timeout: Option<std::time::Duration>) {
*self.idle_timeout.lock().expect("idle_timeout poisoned") =
super::session::reject_zero_deadline(
timeout,
"RpcServer::set_idle_timeout: a zero duration is not a valid deadline; ignoring",
);
}
pub fn set_reply_timeout(&self, timeout: Option<std::time::Duration>) {
*self.reply_timeout.lock().expect("reply_timeout poisoned") =
super::session::reject_zero_deadline(
timeout,
"RpcServer::set_reply_timeout: a zero duration is not a valid deadline; ignoring",
);
}
fn live_worker_count(&self) -> usize {
let mut workers = self.workers.lock().expect("workers poisoned");
workers.retain(|h| !h.is_finished());
workers.len()
}
pub fn set_authorizer<F>(&self, f: F)
where
F: Fn(&PeerIdentity) -> bool + Send + Sync + 'static,
{
*self.authorizer.lock().expect("authorizer poisoned") = Some(Arc::new(f));
}
#[cfg(feature = "test-util")]
#[doc(hidden)]
pub fn __set_attach_shutdown_probe<F>(&self, f: F)
where
F: Fn() + Send + Sync + 'static,
{
*self
.attach_shutdown_probe
.lock()
.expect("attach_shutdown_probe poisoned") = Some(Arc::new(f));
}
fn run_attach_shutdown_probe(&self) {
let probe = self
.attach_shutdown_probe
.lock()
.expect("attach_shutdown_probe poisoned")
.clone();
if let Some(p) = probe {
p();
}
}
pub fn set_supported_fd_modes(&self, modes: &[crate::rpc::FileDescriptorTransportMode]) {
let unix = modes.contains(&crate::rpc::FileDescriptorTransportMode::Unix);
self.fd_unix_supported.store(unix, Ordering::SeqCst);
}
pub fn set_android13plus(&self, max_version: u32) {
let max_version = if super::wire_android13::is_supported_protocol_version(max_version) {
max_version
} else {
let max = super::wire_android13::SUPPORTED_MAX_VERSION;
log::error!("set_android13plus({max_version}): unsupported version, using {max}");
max
};
*self
.wire_max_version
.lock()
.expect("wire_max_version poisoned") = Some(max_version);
}
fn configure_session(&self, session: &RpcSession) {
let root = self.root.lock().expect("root poisoned").clone();
if let Some(root) = root {
if let Err(e) = session.set_root(root) {
log::error!("RPC: server root rejected by the new session: {e:?}");
}
}
let max_threads = *self.max_threads.lock().expect("max_threads poisoned");
session.set_max_threads(max_threads);
let reply_timeout = *self.reply_timeout.lock().expect("reply_timeout poisoned");
session.set_timeout(reply_timeout);
if self.fd_unix_supported.load(Ordering::SeqCst) {
session.set_supported_fd_modes(&[crate::rpc::FileDescriptorTransportMode::Unix]);
}
}
fn make_session(&self, transport: Box<dyn RpcTransport>) -> super::RpcResult<RpcSession> {
let session = RpcSession::new_accepted(transport, super::address::AddressSpace::Acceptor)?;
self.configure_session(&session);
self.track_session(&session.inner_arc());
Ok(session)
}
fn track_session(&self, inner: &Arc<RpcSessionInner>) {
let mut live = self.live_sessions.lock().expect("live_sessions poisoned");
live.retain(|w| w.strong_count() > 0);
live.push(Arc::downgrade(inner));
}
fn minted_after_terminate(&self, session: &RpcSession) -> bool {
if self.terminating.load(Ordering::SeqCst) {
log::debug!("RPC: connection accepted as the server was terminating; ending it");
session.close_session();
return true;
}
false
}
fn register_session(&self, id: RpcSessionId, inner: &Arc<RpcSessionInner>) {
let mut map = self.sessions.lock().expect("sessions poisoned");
map.retain(|_, w| w.strong_count() > 0);
map.insert(id, Arc::downgrade(inner));
drop(map);
self.session_registered.fetch_add(1, Ordering::SeqCst);
}
fn register_r34_session(
&self,
inner: &Arc<RpcSessionInner>,
founder: PeerIdentity,
) -> RpcResult<i32> {
let mut map = self.r34_sessions.lock().expect("r34_sessions poisoned");
map.retain(|_, s| s.inner.strong_count() > 0);
let id = loop {
let mut bytes = [0u8; 4];
getrandom::fill(&mut bytes).map_err(|e| {
super::RpcError::Io(std::io::Error::other(format!(
"CSPRNG getrandom failed for an r34 session id: {e}"
)))
})?;
let id = i32::from_le_bytes(bytes) & i32::MAX;
if id >= 0x100 && !map.contains_key(&id) {
break id;
}
};
map.insert(
id,
R34Session {
inner: Arc::downgrade(inner),
founder,
},
);
drop(map);
inner.set_r34_session_id(id);
self.session_registered.fetch_add(1, Ordering::SeqCst);
Ok(id)
}
fn resolve_r34_session(&self, id: i32) -> Option<(Arc<RpcSessionInner>, PeerIdentity)> {
let map = self.r34_sessions.lock().expect("r34_sessions poisoned");
let entry = map.get(&id)?;
Some((entry.inner.upgrade()?, entry.founder.clone()))
}
fn resolve_session(&self, id: &[u8]) -> Option<Arc<RpcSessionInner>> {
let key = RpcSessionId::try_from_slice(id)?;
self.sessions
.lock()
.expect("sessions poisoned")
.get(&key)
.and_then(std::sync::Weak::upgrade)
}
pub fn session_registered_count(&self) -> usize {
self.session_registered.load(Ordering::SeqCst)
}
pub fn attached_count(&self) -> usize {
self.attached_count.load(Ordering::SeqCst)
}
pub fn rejected_unknown_id_count(&self) -> usize {
self.rejected_unknown_id.load(Ordering::SeqCst)
}
pub fn live_session_node_count(&self) -> usize {
let mut sessions: Vec<_> = self
.sessions
.lock()
.expect("sessions poisoned")
.values()
.filter_map(std::sync::Weak::upgrade)
.collect();
sessions.extend(
self.r34_sessions
.lock()
.expect("r34_sessions poisoned")
.values()
.filter_map(|s| s.inner.upgrade()),
);
sessions.iter().map(|s| s.local_node_count()).sum()
}
pub fn session_live_conns(&self, id: &[u8; 32]) -> Option<usize> {
let key = RpcSessionId::new(*id);
self.sessions
.lock()
.expect("sessions poisoned")
.get(&key)
.and_then(std::sync::Weak::upgrade)
.map(|s| s.live_conn_count())
}
pub fn session_slot_count(&self, id: &[u8; 32]) -> Option<usize> {
let key = RpcSessionId::new(*id);
let inner = self
.sessions
.lock()
.expect("sessions poisoned")
.get(&key)
.and_then(std::sync::Weak::upgrade);
inner.map(|s| s.slot_count())
}
pub fn serve_connection(self: &Arc<Self>, transport: Box<dyn RpcTransport>) {
let server = Arc::clone(self);
let handle = match std::thread::Builder::new()
.name("rpc-conn".into())
.spawn(move || {
Self::run_connection_in_worker(server, transport, None);
}) {
Ok(h) => h,
Err(e) => {
log::warn!("RPC: failed to spawn connection worker, dropping: {e}");
return;
}
};
let mut workers = self.workers.lock().expect("workers poisoned");
workers.retain(|h| !h.is_finished());
workers.push(handle);
}
fn serve_connection_raw(self: &Arc<Self>, raw: RawAccepted) {
let server = Arc::clone(self);
let spawned = std::thread::Builder::new()
.name("rpc-conn".into())
.spawn(move || {
if let Err(e) = raw.prepare_for_worker() {
log::warn!("RPC: failed to prepare accepted stream, dropping: {e:?}");
return;
}
let handshake_timeout = *server
.handshake_timeout
.lock()
.expect("handshake_timeout poisoned");
let admission =
PhaseDeadline::arm_with(handshake_timeout, || raw.shutdown_handle());
if let Some(d) = handshake_timeout {
if let Err(e) = raw.set_read_timeout(Some(d)) {
log::debug!("RPC: failed to arm pre-wrap handshake read timeout: {e:?}");
}
if let Err(e) = raw.set_write_timeout(Some(d)) {
log::debug!("RPC: failed to arm pre-wrap handshake write timeout: {e:?}");
}
}
let transport = match server.wrap_accepted(raw) {
Ok(t) => t,
Err(_) if admission.fired() => {
log::warn!("RPC: handshake deadline passed during the transport wrap");
return;
}
Err(e) => {
log::warn!("RPC transport wrap (TLS or native) failed: {e:?}");
return;
}
};
Self::run_connection_in_worker(server, transport, Some(admission));
});
let handle = match spawned {
Ok(h) => h,
Err(e) => {
log::warn!("RPC: failed to spawn connection worker, dropping: {e}");
return;
}
};
let mut workers = self.workers.lock().expect("workers poisoned");
workers.retain(|h| !h.is_finished());
workers.push(handle);
}
#[cfg(feature = "rpc-tls")]
fn wrap_accepted(&self, raw: RawAccepted) -> RpcResult<Box<dyn RpcTransport>> {
raw.into_transport(self.tls_snapshot())
}
#[cfg(not(feature = "rpc-tls"))]
fn wrap_accepted(&self, raw: RawAccepted) -> RpcResult<Box<dyn RpcTransport>> {
raw.into_transport()
}
fn arm_serve_timeouts(transport: &dyn RpcTransport, idle: Option<std::time::Duration>) {
if let Err(e) = transport.set_read_timeout(idle) {
log::debug!("RPC: failed to set serve-phase read timeout: {e:?}");
}
Self::arm_write_timeout(transport, idle);
}
fn arm_write_timeout(transport: &dyn RpcTransport, idle: Option<std::time::Duration>) {
if let Err(e) = transport.set_write_timeout(idle) {
log::debug!("RPC: failed to set serve-phase write timeout: {e:?}");
}
}
fn run_connection_in_worker(
server: Arc<Self>,
transport: Box<dyn RpcTransport>,
admission: Option<PhaseDeadline>,
) {
let authorizer = server
.authorizer
.lock()
.expect("authorizer poisoned")
.clone();
let handshake_timeout = *server
.handshake_timeout
.lock()
.expect("handshake_timeout poisoned");
let (mut admission, rearm) = match admission {
Some(mut armed) if authorizer.is_some() => match armed.stop() {
Some(left) => (PhaseDeadline::none(), Some(left)),
None => {
log::warn!("RPC: the handshake deadline passed before the authorizer ran");
return;
}
},
Some(armed) => (armed, None),
None => (PhaseDeadline::none(), Some(handshake_timeout)),
};
if let Some(authz) = authorizer {
let peer = transport.peer_identity();
if !authz(&peer) {
log::warn!("RPC connection rejected by authorizer: peer {peer:?}");
return;
}
}
if let Some(after) = rearm {
admission = PhaseDeadline::arm_with(after, || transport.shutdown_handle());
}
if let Some(d) = handshake_timeout {
if let Err(e) = transport.set_read_timeout(Some(d)) {
log::debug!("RPC: failed to arm handshake read timeout: {e:?}");
}
if let Err(e) = transport.set_write_timeout(Some(d)) {
log::debug!("RPC: failed to arm handshake write timeout: {e:?}");
}
}
let a13_max = *server
.wire_max_version
.lock()
.expect("wire_max_version poisoned");
match a13_max {
Some(max) => {
let fd_unix = server.fd_unix_supported.load(Ordering::SeqCst);
let (transport, codec, client_fd_mode, client_id, incoming) =
match RpcSession::android13plus_accept_handshake(transport, max) {
Ok(parts) => parts,
Err(_) if admission.fired() => {
log::warn!("android-13+ RPC handshake: the handshake deadline passed");
return;
}
Err(super::RpcError::Protocol(BAD_SESSION_ID_SIZE)) => {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: client supplied a session id that is not \
32 bytes; rejecting connection"
);
return;
}
Err(e) => {
log::warn!("android-13+ RPC handshake failed: {e}");
return;
}
};
if !admission.disarm() {
log::warn!("android-13+ RPC handshake: the handshake deadline passed");
return;
}
let idle = *server.idle_timeout.lock().expect("idle_timeout poisoned");
if incoming {
if let Err(e) = transport.set_read_timeout(None) {
log::debug!("RPC: failed to clear callback-slot read timeout: {e:?}");
}
Self::arm_write_timeout(transport.as_ref(), idle);
match server.resolve_session(&client_id) {
Some(inner) => {
if inner.wire_protocol_version() != Some(codec.version()) {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: incoming attach codec version {} \
≠ founding inner version {:?}; rejecting",
codec.version(),
inner.wire_protocol_version()
);
drop(transport);
return;
}
server.run_attach_shutdown_probe();
if server.shutdown.load(Ordering::SeqCst) {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: incoming attach after server \
shutdown; rejecting"
);
drop(transport);
return;
}
let incoming_cap =
(inner.max_threads_value() as usize).saturating_mul(2);
let session = RpcSession::wrap_inner(inner);
if let Err(e) =
session.add_callback_slot_and_init(transport, incoming_cap, &codec)
{
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: incoming callback attach refused \
(slot cap {incoming_cap} reached or session torn \
down): {e:?}"
);
}
}
None => {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: incoming connection supplied an \
unknown/stale session id; rejecting"
);
drop(transport);
}
}
return;
}
Self::arm_serve_timeouts(transport.as_ref(), idle);
if client_id.is_empty() {
let session = match RpcSession::from_android13plus(
transport,
codec,
client_fd_mode,
fd_unix,
) {
Ok(s) => s,
Err(e) => {
log::warn!("android-13+ RPC: from_android13plus failed: {e:?}");
return;
}
};
let id = RpcSessionId::new(session.session_id());
server.register_session(id, &session.inner_arc());
server.track_session(&session.inner_arc());
if server.minted_after_terminate(&session) {
return;
}
server.configure_session(&session);
session.set_serve_read_deadline(idle);
session.serve_blocking().log("RPC session ended");
} else if let Some(inner) = server.resolve_session(&client_id) {
if inner.wire_protocol_version() != Some(codec.version()) {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: attach codec version {} ≠ \
founding inner version {:?}; rejecting",
codec.version(),
inner.wire_protocol_version()
);
drop(transport);
return;
}
server.run_attach_shutdown_probe();
if server.shutdown.load(Ordering::SeqCst) {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!("android-13+ RPC: attach after server shutdown; rejecting");
drop(transport);
return;
}
let cap = inner.max_threads_value() as usize;
let session = RpcSession::wrap_inner(inner);
let slot_id = match session.add_incoming_slot_capped(transport, cap) {
Ok(id) => id,
Err(StatusCode::FailedTransaction) => {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: attach refused (incoming slot \
cap reached: max_threads={cap})"
);
return;
}
Err(StatusCode::BadType) => {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: attach refused (transport differs \
from the session's founding connection)"
);
return;
}
Err(e) => {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: session torn down between \
resolve and attach; rejecting: {e:?}"
);
return;
}
};
session.set_serve_read_deadline(idle);
server.attached_count.fetch_add(1, Ordering::SeqCst);
session
.serve_blocking_on(slot_id)
.log("RPC attached connection ended");
} else {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!(
"android-13+ RPC: client supplied an unknown/stale \
session id; rejecting connection"
);
drop(transport);
}
}
None => {
let id = super::wire_android13::read_r34_session_preamble(
&mut super::wire_android13::RawTransportIo(&*transport),
);
match id {
Ok(super::address::RPC_SESSION_ID_NEW) => {}
Ok(id) if admission.disarm() => {
return Self::join_r34_session(&server, id, transport)
}
Ok(_) => {
log::debug!("RPC r34: the handshake deadline passed");
return;
}
Err(_) if admission.fired() => {
log::debug!("RPC r34: the handshake deadline passed");
return;
}
Err(e) => {
log::debug!("RPC r34: no session-id preamble: {e:?}");
return;
}
}
if let Err(e) = transport.set_write_timeout(None) {
log::debug!("RPC r34: failed to lift handshake write deadline: {e:?}");
}
let founder = transport.peer_identity();
let session = match server.make_session(transport) {
Ok(s) => s,
Err(e) => {
log::warn!("RPC r34: make_session failed: {e:?}");
return;
}
};
if let Err(e) = server.register_r34_session(&session.inner_arc(), founder) {
log::warn!("RPC r34: cannot mint a session id: {e:?}");
session.close_session();
return;
}
if server.minted_after_terminate(&session) {
return;
}
session
.serve_blocking_clearing_admission_deadline(
handshake_timeout.is_some(),
admission,
)
.log("RPC session ended");
}
}
}
fn join_r34_session(server: &Arc<Self>, id: i32, transport: Box<dyn RpcTransport>) {
let refuse = |why: &str| {
server.rejected_unknown_id.fetch_add(1, Ordering::SeqCst);
log::warn!("RPC r34: connection asking to join session {id} refused: {why}");
};
let Some((inner, founder)) = server.resolve_r34_session(id) else {
if u32::try_from(id).is_ok_and(super::wire_android13::is_supported_protocol_version) {
log::warn!(
"RPC r34: preamble {id} reads as an android-13+ connection header; \
an android-13+ client needs `RpcServer::set_android13plus`"
);
}
return refuse("no live session has that id");
};
if server.shutdown.load(Ordering::SeqCst) {
return refuse("the server is shutting down");
}
if let PeerIdentity::Local { uid, .. } = transport.peer_identity() {
if !matches!(founder, PeerIdentity::Local { uid: founding, .. } if founding == uid) {
return refuse("its peer is not the founding connection's uid");
}
}
for lifted in [
transport.set_read_timeout(None),
transport.set_write_timeout(None),
] {
if let Err(e) = lifted {
log::debug!("RPC r34: failed to lift a joined connection's deadline: {e:?}");
}
}
let cap = inner.max_threads_value() as usize;
let session = RpcSession::wrap_inner(inner);
let slot_id = match session.add_incoming_slot_capped(transport, cap) {
Ok(slot_id) => slot_id,
Err(StatusCode::FailedTransaction) => {
return refuse(&format!(
"the session has its max_threads={cap} connections"
))
}
Err(StatusCode::BadType) => {
return refuse("its transport differs from the founding connection's")
}
Err(e) => return refuse(&format!("the session ended ({e:?})")),
};
server.attached_count.fetch_add(1, Ordering::SeqCst);
session
.serve_blocking_on(slot_id)
.log("RPC r34 joined connection ended");
}
pub fn run(self: &Arc<Self>) -> Result<()> {
loop {
if self.shutdown.load(Ordering::SeqCst) {
break;
}
let max_connections = *self
.max_connections
.lock()
.expect("max_connections poisoned");
if let Some(max) = max_connections {
if self.live_worker_count() >= max {
std::thread::sleep(std::time::Duration::from_millis(5));
continue;
}
}
match self.listener.accept_raw() {
Ok(raw) => {
self.serve_connection_raw(raw);
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
std::thread::sleep(std::time::Duration::from_millis(5));
}
Err(e)
if matches!(
e.kind(),
std::io::ErrorKind::ConnectionAborted
| std::io::ErrorKind::ConnectionReset
| std::io::ErrorKind::Interrupted
) || matches!(
e.raw_os_error(),
Some(code)
if code == libc::EPROTO
|| code == libc::ENETDOWN
|| code == libc::ENETUNREACH
|| code == libc::EHOSTUNREACH
|| code == libc::ETIMEDOUT
) =>
{
log::warn!("transient accept error, continuing: {e}");
std::thread::sleep(std::time::Duration::from_millis(10));
}
Err(e)
if matches!(
e.raw_os_error(),
Some(code)
if code == libc::EMFILE
|| code == libc::ENFILE
|| code == libc::ENOMEM
|| code == libc::ENOBUFS
) =>
{
log::warn!("accept resource exhaustion, backing off: {e}");
std::thread::sleep(std::time::Duration::from_millis(50));
}
Err(e) => {
log::error!("accept loop ending (fatal): {e}");
return Err(e.into());
}
}
}
Ok(())
}
pub fn run_background(self: &Arc<Self>) -> JoinHandle<()> {
let me = Arc::clone(self);
std::thread::spawn(move || {
if let Err(e) = me.run() {
log::error!("RPC accept loop terminated with error: {e:?}");
}
})
}
pub fn stop_accepting(&self) {
self.shutdown.store(true, Ordering::SeqCst);
}
pub fn terminate(&self) {
self.terminating.store(true, Ordering::SeqCst);
self.shutdown.store(true, Ordering::SeqCst);
loop {
let live: Vec<Arc<RpcSessionInner>> =
std::mem::take(&mut *self.live_sessions.lock().expect("live_sessions poisoned"))
.iter()
.filter_map(std::sync::Weak::upgrade)
.collect();
let handles: Vec<JoinHandle<()>> =
std::mem::take(&mut *self.workers.lock().expect("workers poisoned"));
if live.is_empty() && handles.is_empty() {
return;
}
for session in &live {
session.close();
}
drop(live);
Self::join_handles(handles);
}
}
pub fn join_workers(&self) {
let handles: Vec<_> = std::mem::take(&mut *self.workers.lock().expect("workers poisoned"));
Self::join_handles(handles);
}
fn join_handles(handles: Vec<JoinHandle<()>>) {
let me = std::thread::current().id();
for h in handles {
if h.thread().id() == me {
log::debug!(
"RPC: server ended from a connection worker; not joining its own thread"
);
continue;
}
if h.join().is_err() {
log::warn!("RPC: connection worker panicked");
}
}
}
pub fn path(&self) -> Option<&Path> {
match &self.bind {
BindAddress::Unix { path, .. } => Some(path.as_path()),
#[cfg(any(target_os = "linux", target_os = "android"))]
BindAddress::UnixAbstract => None,
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
BindAddress::Vsock { .. } => None,
#[cfg(feature = "rpc-tls")]
BindAddress::Tcp(_) => None,
}
}
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
pub fn vsock_address(&self) -> Option<(u32, u32)> {
match &self.bind {
BindAddress::Vsock { cid, port } => Some((*cid, *port)),
BindAddress::Unix { .. } => None,
BindAddress::UnixAbstract => None,
#[cfg(feature = "rpc-tls")]
BindAddress::Tcp(_) => None,
}
}
#[cfg(feature = "rpc-tls")]
pub fn tcp_address(&self) -> Option<SocketAddr> {
match &self.bind {
BindAddress::Tcp(addr) => Some(*addr),
BindAddress::Unix { .. } => None,
#[cfg(any(target_os = "linux", target_os = "android"))]
BindAddress::UnixAbstract => None,
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
BindAddress::Vsock { .. } => None,
}
}
}
impl Drop for RpcServer {
fn drop(&mut self) {
self.shutdown.store(true, Ordering::SeqCst);
match &self.bind {
BindAddress::Unix { path, file } => {
if file.is_some() && socket_file_id(path) == *file {
let _ = std::fs::remove_file(path);
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
BindAddress::UnixAbstract => {}
#[cfg(all(feature = "rpc-vsock", any(target_os = "linux", target_os = "android")))]
BindAddress::Vsock { .. } => {
}
#[cfg(feature = "rpc-tls")]
BindAddress::Tcp(_) => {
}
}
}
}
impl RpcSession {
pub fn get_service(&self, name: &str) -> Result<SIBinder> {
let root = self.get_root()?;
let rp = (*root)
.as_any()
.downcast_ref::<super::proxy::RpcProxy>()
.ok_or(StatusCode::BadType)?;
let mut data = rp.build_request(DIRECTORY_DESC)?;
data.write(&name)?;
let mut reply = rp
.transact(TX_GET_SERVICE, &data, 0)?
.ok_or(StatusCode::UnexpectedNull)?;
let st: crate::Status = reply.read()?;
if !st.is_ok() {
return Err(StatusCode::from(st));
}
reply.read::<SIBinder>()
}
pub fn get_interface<T: crate::FromIBinder + ?Sized>(
&self,
name: &str,
) -> Result<crate::Strong<T>> {
crate::Strong::<T>::try_from(self.get_service(name)?)
}
}