rpcs #54
@ -1,4 +1,6 @@
|
|||||||
use super::{Spawner, TriggerData};
|
use log::info;
|
||||||
|
|
||||||
|
use super::{RpcHandler, Spawner, TriggerData};
|
||||||
use crate::specification::{Arg, FileSocket, Pipe};
|
use crate::specification::{Arg, FileSocket, Pipe};
|
||||||
use crate::void::VoidBuilder;
|
use crate::void::VoidBuilder;
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
@ -7,7 +9,10 @@ use std::ffi::CString;
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::net::TcpListener;
|
use std::net::TcpListener;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
use std::os::unix::io::IntoRawFd;
|
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||||
|
|
||||||
|
use nix::sys::socket;
|
||||||
|
use nix::unistd::{fork, ForkResult};
|
||||||
|
|
||||||
pub struct PreparedArgs(Vec<PreparedArg>);
|
pub struct PreparedArgs(Vec<PreparedArg>);
|
||||||
|
|
||||||
@ -89,6 +94,9 @@ enum PreparedArg {
|
|||||||
/// A TCP Listener
|
/// A TCP Listener
|
||||||
TcpListener { socket: TcpListener },
|
TcpListener { socket: TcpListener },
|
||||||
|
|
||||||
|
/// RPC
|
||||||
|
Rpc { socket: File },
|
||||||
|
|
||||||
/// The rest of argv[1..], 0 or more arguments
|
/// The rest of argv[1..], 0 or more arguments
|
||||||
Trailing,
|
Trailing,
|
||||||
}
|
}
|
||||||
@ -154,6 +162,45 @@ impl PreparedArg {
|
|||||||
PreparedArg::TcpListener { socket }
|
PreparedArg::TcpListener { socket }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Arg::Rpc(specs) => {
|
||||||
|
let (ambient, void) = socket::socketpair(
|
||||||
|
socket::AddressFamily::Unix,
|
||||||
|
socket::SockType::Datagram,
|
||||||
|
None,
|
||||||
|
socket::SockFlag::empty(),
|
||||||
|
)
|
||||||
|
.map_err(|e| Error::Nix {
|
||||||
|
msg: "socketpair",
|
||||||
|
src: e,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// SAFETY: valid new fd as socketpair(2) returned successfully
|
||||||
|
let ambient = unsafe { File::from_raw_fd(ambient) };
|
||||||
|
// SAFETY: valid new fd as socketpair(2) returned successfully
|
||||||
|
let void = unsafe { File::from_raw_fd(void) };
|
||||||
|
|
||||||
|
// spawn this child with ambient authority
|
||||||
|
// necessary as no void ever has outgoing network capability
|
||||||
|
// SAFETY: this program is single-threaded so no safety issue
|
||||||
|
let child = unsafe { fork() }.map_err(|e| Error::Nix {
|
||||||
|
msg: "fork",
|
||||||
|
src: e,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match child {
|
||||||
|
ForkResult::Child => {
|
||||||
|
let handler = RpcHandler::new(specs);
|
||||||
|
handler.handle(ambient).unwrap();
|
||||||
|
}
|
||||||
|
ForkResult::Parent { child } => {
|
||||||
|
info!("spawned rpc handler with pid {}", child);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: safe as socketpair returned successfully
|
||||||
|
PreparedArg::Rpc { socket: void }
|
||||||
|
}
|
||||||
|
|
||||||
Arg::BinaryName => PreparedArg::BinaryName,
|
Arg::BinaryName => PreparedArg::BinaryName,
|
||||||
Arg::Entrypoint => PreparedArg::Entrypoint,
|
Arg::Entrypoint => PreparedArg::Entrypoint,
|
||||||
Arg::Trigger => PreparedArg::Trigger,
|
Arg::Trigger => PreparedArg::Trigger,
|
||||||
@ -191,6 +238,10 @@ impl PreparedArg {
|
|||||||
Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()])
|
Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreparedArg::Rpc { socket } => {
|
||||||
|
Ok(vec![CString::new(socket.into_raw_fd().to_string()).unwrap()])
|
||||||
|
}
|
||||||
|
|
||||||
PreparedArg::Trailing => Ok(spawner
|
PreparedArg::Trailing => Ok(spawner
|
||||||
.binary_args
|
.binary_args
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
|
mod rpc;
|
||||||
|
|
||||||
use args::PreparedArgs;
|
use args::PreparedArgs;
|
||||||
|
use rpc::RpcHandler;
|
||||||
|
|
||||||
use crate::specification::{Arg, Entrypoint, Environment, Specification, Trigger};
|
use crate::specification::{Arg, Entrypoint, Environment, Specification, Trigger};
|
||||||
use crate::void::VoidBuilder;
|
use crate::void::VoidBuilder;
|
||||||
|
300
src/spawner/rpc.rs
Normal file
300
src/spawner/rpc.rs
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
use log::{debug, error};
|
||||||
|
|
||||||
|
use crate::specification::{AddressFamily as SpecAddressFamily, RpcSpecification};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::net::{TcpStream, UdpSocket};
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::os::unix::io::AsRawFd;
|
||||||
|
|
||||||
|
use nix::sys::socket::AddressFamily;
|
||||||
|
use nix::sys::socket::{recv, send, sendmsg, ControlMessage, MsgFlags};
|
||||||
|
|
||||||
|
const MAX_MSG_LENGTH: usize = 4096;
|
||||||
|
|
||||||
|
pub struct RpcHandler<'a> {
|
||||||
|
permitted_rpcs: &'a [RpcSpecification],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RpcHandler<'a> {
|
||||||
|
pub(super) fn new(permitted_rpcs: &'a [RpcSpecification]) -> Self {
|
||||||
|
Self { permitted_rpcs }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn handle(&self, socket: File) -> Result<(), Error> {
|
||||||
|
let mut buf = vec![0; MAX_MSG_LENGTH];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let read_bytes =
|
||||||
|
recv(socket.as_raw_fd(), &mut buf, MsgFlags::empty()).map_err(|e| Error::Nix {
|
||||||
|
msg: "recvmsg",
|
||||||
|
src: e,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
debug!("handling rpc");
|
||||||
|
|
||||||
|
if read_bytes < 4 {
|
||||||
|
error!("received rpc too short");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: safe as the enum repr is non_exhaustive so any value is valid and the buffer is long enough
|
||||||
|
let kind = unsafe { *(buf.as_ptr() as *const RpcKind) };
|
||||||
|
|
||||||
|
let fds = Vec::new();
|
||||||
|
if kind.num_fds() > 0 {
|
||||||
|
// get any fds to go alongside the message
|
||||||
|
// nothing which requires this currently exists
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
let resp = handle_rpc(self.permitted_rpcs, kind, &buf[4..], &fds);
|
||||||
|
|
||||||
|
let (msg, fds) = RpcResultSend::new(resp);
|
||||||
|
|
||||||
|
// sendmsg first so its there when listening for the send
|
||||||
|
if !fds.is_empty() {
|
||||||
|
let fds: Box<[i32]> = fds.iter().map(|f| f.as_raw_fd()).collect();
|
||||||
|
|
||||||
|
sendmsg::<()>(
|
||||||
|
socket.as_raw_fd(),
|
||||||
|
&[],
|
||||||
|
&[ControlMessage::ScmRights(&fds)],
|
||||||
|
MsgFlags::empty(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.map_err(|e| Error::Nix {
|
||||||
|
msg: "sendmsg",
|
||||||
|
src: e,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: safe as msg is of fixed size
|
||||||
|
let msg = unsafe {
|
||||||
|
std::slice::from_raw_parts(
|
||||||
|
&msg as *const RpcResultSend as *const u8,
|
||||||
|
std::mem::size_of_val(&msg),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
send(socket.as_raw_fd(), msg, MsgFlags::empty()).map_err(|e| Error::Nix {
|
||||||
|
msg: "send",
|
||||||
|
src: e,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u32)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum RpcKind {
|
||||||
|
OpenTcpSocket,
|
||||||
|
OpenUdpSocket,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcKind {
|
||||||
|
fn num_fds(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
RpcKind::OpenTcpSocket => 0,
|
||||||
|
RpcKind::OpenUdpSocket => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OpenSocket {
|
||||||
|
pub family: AddressFamily,
|
||||||
|
pub port: u16,
|
||||||
|
pub host: [c_char],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RpcResult {
|
||||||
|
OpenTcpSocket { socket: TcpStream },
|
||||||
|
OpenUdpSocket { socket: UdpSocket },
|
||||||
|
|
||||||
|
Error { error: RpcError },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RpcResultSend {
|
||||||
|
OpenTcpSocket,
|
||||||
|
OpenUdpSocket,
|
||||||
|
|
||||||
|
Error { error: RpcError },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpcResultSend {
|
||||||
|
fn new(from: RpcResult) -> (Self, Vec<Box<dyn AsRawFd>>) {
|
||||||
|
match from {
|
||||||
|
RpcResult::OpenTcpSocket { socket } => (Self::OpenTcpSocket, vec![Box::new(socket)]),
|
||||||
|
RpcResult::OpenUdpSocket { socket } => (Self::OpenUdpSocket, vec![Box::new(socket)]),
|
||||||
|
RpcResult::Error { error } => (Self::Error { error }, vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub enum RpcError {
|
||||||
|
BadlyFormedRequest,
|
||||||
|
OperationNotPermitted,
|
||||||
|
Io { errno: i32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_rpc(
|
||||||
|
permitted_rpcs: &[RpcSpecification],
|
||||||
|
kind: RpcKind,
|
||||||
|
data: &[u8],
|
||||||
|
_fds: &[File],
|
||||||
|
) -> RpcResult {
|
||||||
|
fn inner(
|
||||||
|
permitted_rpcs: &[RpcSpecification],
|
||||||
|
kind: RpcKind,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<RpcResult, RpcError> {
|
||||||
|
match kind {
|
||||||
|
RpcKind::OpenTcpSocket => {
|
||||||
|
let data = unsafe { &*(data as *const [u8] as *const OpenSocket) };
|
||||||
|
if !validate_open_tcp_socket(permitted_rpcs, data)? {
|
||||||
|
Ok(RpcResult::Error {
|
||||||
|
error: RpcError::OperationNotPermitted,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
handle_open_tcp_socket(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RpcKind::OpenUdpSocket => {
|
||||||
|
let data = unsafe { &*(data as *const [u8] as *const OpenSocket) };
|
||||||
|
if !validate_open_udp_socket(permitted_rpcs, data)? {
|
||||||
|
Ok(RpcResult::Error {
|
||||||
|
error: RpcError::OperationNotPermitted,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
handle_open_udp_socket(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match inner(permitted_rpcs, kind, data) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => RpcResult::Error { error: e },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_open_tcp_socket(
|
||||||
|
permitted_rpcs: &[RpcSpecification],
|
||||||
|
req: &OpenSocket,
|
||||||
|
) -> Result<bool, RpcError> {
|
||||||
|
for each in permitted_rpcs {
|
||||||
|
if let RpcSpecification::OpenTcpSocket { family, port, host } = each {
|
||||||
|
let mut allowed = true;
|
||||||
|
|
||||||
|
allowed &= match family {
|
||||||
|
None => true,
|
||||||
|
Some(fam) => match req.family {
|
||||||
|
AddressFamily::Inet => *fam == SpecAddressFamily::Inet,
|
||||||
|
AddressFamily::Inet6 => *fam == SpecAddressFamily::Inet6,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
allowed &= match port {
|
||||||
|
None => true,
|
||||||
|
Some(p) => req.port == *p,
|
||||||
|
};
|
||||||
|
|
||||||
|
allowed &= match host {
|
||||||
|
None => true,
|
||||||
|
Some(h) => {
|
||||||
|
CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
||||||
|
.map_err(|_| RpcError::BadlyFormedRequest)?
|
||||||
|
.to_string_lossy()
|
||||||
|
.as_ref()
|
||||||
|
== h
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if allowed {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_open_tcp_socket(req: &OpenSocket) -> Result<RpcResult, RpcError> {
|
||||||
|
let host = CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
||||||
|
.map_err(|_| RpcError::BadlyFormedRequest)?;
|
||||||
|
let host = host.to_str().map_err(|_| RpcError::BadlyFormedRequest)?;
|
||||||
|
|
||||||
|
let socket = TcpStream::connect(host).map_err(|e| RpcError::Io {
|
||||||
|
errno: e.raw_os_error().unwrap(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(RpcResult::OpenTcpSocket { socket })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_open_udp_socket(
|
||||||
|
permitted_rpcs: &[RpcSpecification],
|
||||||
|
req: &OpenSocket,
|
||||||
|
) -> Result<bool, RpcError> {
|
||||||
|
for each in permitted_rpcs {
|
||||||
|
if let RpcSpecification::OpenUdpSocket { family, port, host } = each {
|
||||||
|
let mut allowed = true;
|
||||||
|
|
||||||
|
allowed &= match family {
|
||||||
|
None => true,
|
||||||
|
Some(fam) => match req.family {
|
||||||
|
AddressFamily::Inet => *fam == SpecAddressFamily::Inet,
|
||||||
|
AddressFamily::Inet6 => *fam == SpecAddressFamily::Inet6,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
allowed &= match port {
|
||||||
|
None => true,
|
||||||
|
Some(p) => req.port == *p,
|
||||||
|
};
|
||||||
|
|
||||||
|
allowed &= match host {
|
||||||
|
None => true,
|
||||||
|
Some(h) => {
|
||||||
|
CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
||||||
|
.map_err(|_| RpcError::BadlyFormedRequest)?
|
||||||
|
.to_string_lossy()
|
||||||
|
.as_ref()
|
||||||
|
== h
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if allowed {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_open_udp_socket(req: &OpenSocket) -> Result<RpcResult, RpcError> {
|
||||||
|
let host = CStr::from_bytes_with_nul(as_u8_slice(&req.host))
|
||||||
|
.map_err(|_| RpcError::BadlyFormedRequest)?;
|
||||||
|
let host = host.to_str().map_err(|_| RpcError::BadlyFormedRequest)?;
|
||||||
|
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| RpcError::Io {
|
||||||
|
errno: e.raw_os_error().unwrap(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
socket.connect(host).map_err(|e| RpcError::Io {
|
||||||
|
errno: e.raw_os_error().unwrap(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(RpcResult::OpenUdpSocket { socket })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_u8_slice(s: &[c_char]) -> &[u8] {
|
||||||
|
unsafe { std::slice::from_raw_parts(s.as_ptr() as *const u8, s.len()) }
|
||||||
|
}
|
@ -68,6 +68,9 @@ pub enum Arg {
|
|||||||
/// A TCP Listener
|
/// A TCP Listener
|
||||||
TcpListener { addr: SocketAddr },
|
TcpListener { addr: SocketAddr },
|
||||||
|
|
||||||
|
/// An RPC socket that accepts specified commands
|
||||||
|
Rpc(Vec<RpcSpecification>),
|
||||||
|
|
||||||
/// The rest of argv[1..], 0 or more arguments
|
/// The rest of argv[1..], 0 or more arguments
|
||||||
Trailing,
|
Trailing,
|
||||||
}
|
}
|
||||||
@ -78,6 +81,38 @@ impl Arg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
pub enum RpcSpecification {
|
||||||
|
/// Open a TCP socket
|
||||||
|
///
|
||||||
|
/// None for each value means that any value is allowed in the call.
|
||||||
|
/// A specified value restricts to exactly that.
|
||||||
|
OpenTcpSocket {
|
||||||
|
family: Option<AddressFamily>,
|
||||||
|
port: Option<u16>,
|
||||||
|
host: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Open a UDP socket
|
||||||
|
///
|
||||||
|
/// None for each value means that any value is allowed in the call.
|
||||||
|
/// A specified value restricts to exactly that.
|
||||||
|
OpenUdpSocket {
|
||||||
|
family: Option<AddressFamily>,
|
||||||
|
port: Option<u16>,
|
||||||
|
host: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
pub enum AddressFamily {
|
||||||
|
/// IPv4 address
|
||||||
|
Inet,
|
||||||
|
|
||||||
|
/// IPv6 address
|
||||||
|
Inet6,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||||
pub enum Pipe {
|
pub enum Pipe {
|
||||||
Rx(String),
|
Rx(String),
|
||||||
|
Loading…
Reference in New Issue
Block a user