I want to transfer a socket between 2 unrelated processes.
Process 1 creates and listens on the socket, it then attaches shared memory and stores the socket file descriptor in this shared memory and awaits on signal SIGUSR1.
Process 2 attaches to the shared, attempts to transfer the file descriptor, then sends the signal SIGUSR1.
On calling pidfd_getfd in process 2, I get the error EPERM.
Process 1 output
fd: Data { socket: 3, pid: Pid(167046) }
Process 2 output
fd: Data { socket: 3, pid: Pid(167046) }
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: EPERM', src/bin/receive.rs:44:74
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Process 1
use std::os::fd::{AsRawFd, RawFd};
use nix::fcntl;
use nix::sys::{
mman, signal,
socket::{self, SockaddrLike},
};
use nix::unistd::{ftruncate, Pid};
#[derive(Debug)]
#[repr(C)]
struct Data {
socket: RawFd,
pid: Pid,
}
const PATH: &str = "/some_arbitrary_path_3";
fn main() {
// Create a TCP socket listening on localhost:8080
// ---------------------------------------------------------------------------------------------
let socket = socket::socket(
socket::AddressFamily::Inet6,
socket::SockType::Stream,
socket::SockFlag::empty(),
None,
)
.unwrap();
let local_host = libc::in6_addr {
s6_addr: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
};
let addr = libc::sockaddr_in6 {
sin6_family: u16::try_from(libc::AF_INET6).unwrap(),
sin6_port: 8765,
sin6_flowinfo: u32::default(),
sin6_addr: local_host,
sin6_scope_id: u32::default(),
};
let addr = unsafe {
socket::SockaddrIn6::from_raw(
std::ptr::addr_of!(addr).cast(),
Some(u32::try_from(std::mem::size_of::<libc::sockaddr_in6>()).unwrap()),
)
.unwrap()
};
socket::bind(socket, &addr).unwrap();
socket::listen(socket, 64).unwrap();
// Store socket file descriptor in shared memory
// ---------------------------------------------------------------------------------------------
let shared_memory_object = std::mem::ManuallyDrop::new(
mman::shm_open(
PATH,
fcntl::OFlag::O_RDWR | fcntl::OFlag::O_CREAT | fcntl::OFlag::O_EXCL,
// TODO: Restrict these to minimum (likely read+write group)
// Uses full permissions
nix::sys::stat::Mode::all(),
)
.unwrap(),
);
let length = std::mem::size_of::<Data>();
ftruncate(shared_memory_object.as_raw_fd(), length as i64).unwrap();
let mapped_shared_memory = unsafe {
mman::mmap(
None,
std::num::NonZeroUsize::new(length).unwrap(),
mman::ProtFlags::PROT_WRITE | mman::ProtFlags::PROT_READ,
mman::MapFlags::MAP_SHARED,
Some(&*shared_memory_object),
0,
)
.unwrap()
};
let ptr = mapped_shared_memory.cast::<Data>();
let pid = Pid::this();
unsafe {
std::ptr::write(ptr, Data { socket, pid });
}
println!("fd: {:?}", unsafe { &*ptr });
// Await SIGUSR1
// ---------------------------------------------------------------------------------------------
let mut sigset = signal::SigSet::empty();
sigset.add(signal::Signal::SIGUSR1);
sigset.wait().unwrap();
}
Process 2
use std::os::fd::AsRawFd;
use std::os::fd::RawFd;
use nix::fcntl;
use nix::sys::{mman, pidfd, signal::Signal};
use nix::unistd::Pid;
#[derive(Debug)]
#[repr(C)]
struct Data {
socket: RawFd,
pid: Pid,
}
const PATH: &str = "/some_arbitrary_path_3";
fn main() {
// Get shared memory object.
let shared_memory_object = std::mem::ManuallyDrop::new(
mman::shm_open(PATH, fcntl::OFlag::O_RDWR, nix::sys::stat::Mode::all()).unwrap(),
);
let length = std::mem::size_of::<Data>();
// Map shared memory.
let mapped_shared_memory = unsafe {
mman::mmap(
None,
std::num::NonZeroUsize::new(length).unwrap(),
mman::ProtFlags::PROT_WRITE | mman::ProtFlags::PROT_READ,
mman::MapFlags::MAP_SHARED,
Some(&*shared_memory_object),
0,
)
.unwrap()
};
// Read data.
let ptr = mapped_shared_memory.cast::<Data>();
let data = unsafe { &mut *ptr };
println!("fd: {:?}", data);
// Transfer socket file descriptor.
let pid_fd = pidfd::pid_open(data.pid, false).unwrap();
let new_socket = pidfd::pidfd_getfd(pid_fd.as_raw_fd(), data.socket).unwrap();
data.socket = new_socket.as_raw_fd();
// Send SIGUSR1
pidfd::pidfd_send_signal(pid_fd, Signal::SIGUSR1, None).unwrap();
}
Dependencies
The dependencies I'm using here are:
nix = { git = "https://github.com/JonathanWoollett-Light/nix", rev = "747b7abf9e2dd57d6e52b7d9288f836780f0ec15" }
libc = "0.2.139" # https://crates.io/crates/libc/0.2.139
There are a few ways to circumvent this.
Run the process as privileged (e.g. sudo).
Use ptrace in the ancestor to give the successor permissions. While this can work, it is very tricky and it is using an extremely over-complicated tool for what we are doing here.
Send the file descriptor over a UDS (Unix Domain Socket), this works, but sending file descriptors over a UDS is ugly.
Enable the SO_REUSEPORT option on the socket such that the new process can use the socket without needing to transfer the file descriptor. This is my preferred solution.
Related
I am trying to run a streamlit server behind the scenes of a Tauri application.
The streamlit server is packaged with PyInstaller into a single binary file and works as expected when standalone.
I have a rust main.rs file that needs to run a streamlit binary using a Command (in this case its a tauri::api::process::Command using a new_sidecar).
It spawns the server, but when the app closes, my streamlit server is not disposed off.
On window exit, I want to send a kill command to kill the server instance in the child.
Here is an example of my code:
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use async_std::task;
use std::sync::mpsc::sync_channel;
use std::thread;
use std::time::Duration;
use tauri::api::process::{Command, CommandEvent};
use tauri::{Manager, WindowEvent};
fn main() {
let (tx_kill, rx_kill) = sync_channel(1);
tauri::Builder::default()
.setup(|app| {
println!("App Setup Start");
let t = Command::new_sidecar("streamlit").expect("failed to create sidecar");
let (mut rx, child) = t.spawn().expect("Failed to spawn server");
let splashscreen_window = app.get_window("splashscreen").unwrap();
let main_window = app.get_window("main").unwrap();
// Listen for server port then refresh main window
tauri::async_runtime::spawn(async move {
while let Some(event) = rx.recv().await {
if let CommandEvent::Stdout(output) = event {
if output.contains("Network URL:") {
let tokens: Vec<&str> = output.split(":").collect();
let port = tokens.last().unwrap();
println!("Connect to port {}", port);
main_window.eval(&format!(
"window.location.replace('http://localhost:{}')",
port
));
task::sleep(Duration::from_secs(2)).await;
splashscreen_window.close().unwrap();
main_window.show().unwrap();
}
}
}
});
// Listen for kill command
thread::spawn(move || loop {
let event = rx_kill.recv();
if event.unwrap() == -1 {
child.kill().expect("Failed to close API");
}
});
Ok(())
})
.on_window_event(move |event| match event.event() {
WindowEvent::Destroyed => {
println!("Window destroyed");
tx_kill.send(-1).expect("Failed to send close signal");
}
_ => {}
})
.run(tauri::generate_context!())
.expect("error while running application");
}
But I am getting an error, when I try to kill the child instance:
thread::spawn(move || loop {
let event = rx_kill.recv();
if event.unwrap() == -1 {
child.kill().expect("Failed to close API");
}
});
use of moved value: `child`
move occurs because `child` has type `CommandChild`, which does not implement the `Copy` trait
Any ideas?
I referred this and also tried tungstenite library. But I was able to run only one server at a time, it captured whole thread.
I tried running multiple servers on different thread but that never listen anything and just exit the program.
Is there anyway that I can run multiple WebSocket servers on different ports, and create, destroy a server in runtime?
Edit: If I run a server on main thread and another one on other thread, it works, looks like I'd have to keep main thread busy somehow.. but is there any better way?
here's some example code:
it uses:
use std::net::TcpListener;
use std::thread::spawn;
use tungstenite::accept;
this is the normal code that blocks the main thread
let server = TcpListener::bind("127.0.0.1:9002").expect("err: ");
for stream in server.incoming() {
spawn(move || {
let mut websocket = accept(stream.unwrap()).unwrap();
loop {
let msg = websocket.read_message().unwrap();
println!("{}", msg);
// We do not want to send back ping/pong messages.
if msg.is_binary() || msg.is_text() {
websocket.write_message(msg).unwrap();
}
}
});
}
here's the code with thread:
spawn(|| {
let server = TcpListener::bind("127.0.0.1:9001").expect("err: ");
for stream in server.incoming() {
spawn(move || {
let mut websocket = accept(stream.unwrap()).unwrap();
loop {
let msg = websocket.read_message().unwrap();
println!("{}", msg);
// We do not want to send back ping/pong messages.
if msg.is_binary() || msg.is_text() {
websocket.write_message(msg).unwrap();
}
}
});
}
});
but the above code needs the main thread to run somehow, I'm indeed able to run multiple servers on different threads but need something to occupy main thread.
Rust programs terminate when the end of main() is reached. What you need to do is wait until your secondary threads have finished.
std::thread::spawn returns a JoinHandle, which has a join method which does exactly that - it waits (blocks) until the thread that the handle refers to finishes, and returns an error if the thread panicked.
So, to keep your program alive as long as any threads are running, you need to collect all of these handles, and join() them one by one. Unlike a busy-loop, this will not waste CPU resources unnecessarily.
use std::net::TcpListener;
use std::thread::spawn;
use tungstenite::accept;
fn main() {
let mut handles = vec![];
// Spawn 3 identical servers on ports 9001, 9002, 9003
for i in 1..=3 {
let handle = spawn(move || {
let server = TcpListener::bind(("127.0.0.1", 9000 + i)).expect("err: ");
for stream in server.incoming() {
spawn(move || {
let mut websocket = accept(stream.unwrap()).unwrap();
loop {
let msg = websocket.read_message().unwrap();
println!("{}", msg);
// We do not want to send back ping/pong messages.
if msg.is_binary() || msg.is_text() {
websocket.write_message(msg).unwrap();
}
}
});
}
});
handles.push(handle);
}
// Wait for each thread to finish before exiting
for handle in handles {
if let Err(e) = handle.join() {
eprintln!("{:?}", e)
}
}
}
When you do all the work in a thread (or threads) and the main thread has nothing to do, usually it is set to wait (join) that thread.
This has the additional advantage that if your secondary thread finishes or panics, then your program will also finish. Or you can wrap the whole create-thread/join-thread in a loop and make it more resilient:
fn main() {
loop {
let th = std::thread::spawn(|| {
// Do the real work here
std::thread::sleep(std::time::Duration::from_secs(1));
panic!("oh!");
});
if let Err(e) = th.join() {
eprintln!("Thread panic: {:?}", e)
}
}
}
Link to playground, I've changed to the loop into a for _ in ..3 because playgrond does not like infinite loops.
I'm trying to pass a file descriptor to the ioctl system call.
I'm trying to link a loop device to a file so I can then set an offset so I can mount it correctly.
I have the following code snippet:
use std::fs::{File, OpenOptions};
use std::os::unix::io::{AsRawFd, RawFd};
use nix::{ioctl_none, ioctl_write_ptr};
use crate::consts::consts::{MAGIC_NUMBER_SIZE, SIGNATURE_SIZE};
const LOOP_MAGIC_BIT: u8 = 0x4C;
const LOOP_SET_FD: u32 = 0x4C00;
const LOOP_SET_STATUS64: u32 = 0x4C04;
const LOOP_CTL_GET_FREE: u32 = 0x4C82;
ioctl_none!(loopback_read_free_device, LOOP_MAGIC_BIT, LOOP_CTL_GET_FREE);
ioctl_write_ptr!(loopback_set_device_fd, LOOP_MAGIC_BIT, LOOP_SET_FD, RawFd);
ioctl_write_ptr!(loopback_set_device_info, LOOP_MAGIC_BIT, LOOP_SET_STATUS64, LoopbackInfo);
pub struct Loopback {}
pub struct LoopbackInfo {
pub io_offset: u64
}
impl Loopback {
pub fn mount_loopback_device(file_path: &str) -> String {
// Open the loopback control device
let loopback_control = File::open("/dev/loop-control");
// Check if is open correctly
match loopback_control {
Ok(control_fd) => {
unsafe {
// Format the device path
let result = format!("/dev/loop{}", loopback_read_free_device(control_fd.as_raw_fd()).unwrap());
// Open the device and the container
let container_file = OpenOptions::new().read(true).write(false).open(file_path);
let device_file = OpenOptions::new().read(true).write(true).open(result.clone());
// Check if is open correctly both files
if container_file.is_err() {
panic!("[Error]: Failed to open the application file");
}
if device_file.is_err() {
panic!("[Error]: Failed to open the device file");
}
// Get the raw pointer from the files
let raw_device_fd = device_file.unwrap().as_raw_fd();
let raw_container_fd = container_file.unwrap().as_raw_fd();
// Match file and device
match loopback_set_device_fd(raw_device_fd, raw_container_fd as *const RawFd) {
Ok(_) => {
// Prepare new offset
let loop_info = LoopbackInfo {
io_offset : (MAGIC_NUMBER_SIZE + SIGNATURE_SIZE) as u64 // Magic Number + Signature Offset
};
// Set the device information
loopback_set_device_info(raw_device_fd, &loop_info as *const LoopbackInfo).unwrap();
// Return the path of the prepared device
return result.clone();
}
Err(code) => panic!("[Error]: Failed to associate the file with the device, error code: {}", code)
}
}
}
Err(_) => panic!("[Error]: It is impossible to get control over the loopback device")
}
}
}
When I try to execute these lines, I get the following panic:
[Error]: Failed to associate the file with the device, error code: EBADF: Bad file number
It seems that it is a bug in the rust nix library, I will try to report it shortly. I changed ioctl_write_ptr to ioctl_write_ptr_bad and it worked as it should.
I'm trying to interact with an external command (in this case, exiftool) and reading the output byte by byte as in the example below.
While I can get it to work if I'm willing to first read in all the output and wait for the child process to finish, using a BufReader seems to result in indefinitely waiting for the first byte. I used this example as reference for accessing stdout with a BufReader.
use std::io::{Write, Read};
use std::process::{Command, Stdio, ChildStdin, ChildStdout};
fn main() {
let mut child = Command::new("exiftool")
.arg("-#") // "Read command line options from file"
.arg("-") // use stdin for -#
.arg("-q") // "quiet processing" (only send image data to stdout)
.arg("-previewImage") // for extracting thumbnails
.arg("-b") // "Output metadata in binary format"
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn().unwrap();
{
// Pass input file names via stdin
let stdin: &mut ChildStdin = child.stdin.as_mut().unwrap();
stdin.write_all("IMG_1709.CR2".as_bytes()).unwrap();
// Leave scope:
// "When an instance of ChildStdin is dropped, the ChildStdin’s underlying file handle will
// be closed."
}
// This doesn't work:
let stdout: ChildStdout = child.stdout.take().unwrap();
let reader = std::io::BufReader::new(stdout);
for (byte_i, byte_value) in reader.bytes().enumerate() {
// This line is never printed and the program doesn't seem to terminate:
println!("Obtained byte {}: {}", byte_i, byte_value.unwrap());
// …
break;
}
// This works:
let output = child.wait_with_output().unwrap();
for (byte_i, byte_value) in output.stdout.iter().enumerate() {
println!("Obtained byte {}: {}", byte_i, byte_value);
// …
break;
}
}
You're not closing the child's stdin. Your stdin variable is a mutable reference, and dropping that has no effect on the referenced ChildStdin.
Use child.stdin.take() instead of child.stdin.as_mut():
{
// Pass input file names via stdin
let stdin: ChildStdin = child.stdin.take().unwrap();
stdin.write_all("IMG_1709.CR2".as_bytes()).unwrap();
// Leave scope:
// "When an instance of ChildStdin is dropped, the ChildStdin’s underlying file handle will
// be closed."
}
I am replacing synchronous socket code written in Rust with the asynchronous equivalent using Tokio. Tokio uses futures for asynchronous activity so tasks are chained together and queued onto an executor to be executed by a thread pool.
The basic pseudocode for what I want to do is like this:
let tokio::net::listener = TcpListener::bind(&sock_addr).unwrap();
let server_task = listener.incoming().for_each(move |socket| {
let in_buf = vec![0u8; 8192];
// TODO this should happen continuously until an error happens
let read_task = tokio::io::read(socket, in_buf).and_then(move |(socket, in_buf, bytes_read)| {
/* ... Logic I want to happen repeatedly as bytes are read ... */
Ok(())
};
tokio::spawn(read_task);
Ok(())
}).map_err(|err| {
error!("Accept error = {:?}", err);
});
tokio::run(server_task);
This pseudocode would only execute my task once. How do I run it continuously? I want it to execute and then execute again and again etc. I only want it to stop executing if it panics or has an error result code. What's the simplest way of doing that?
Using loop_fn should work:
let read_task =
futures::future::loop_fn((socket, in_buf, 0), |(socket, in_buf, bytes_read)| {
if bytes_read > 0 { /* handle bytes */ }
tokio::io::read(socket, in_buf).map(Loop::Continue)
});
A clean way to accomplish this and not have to fight the type system is to use tokio-codec crate; if you want to interact with the reader as a stream of bytes instead of defining a codec you can use tokio_codec::BytesCodec.
use tokio::codec::Decoder;
use futures::Stream;
...
let tokio::net::listener = TcpListener::bind(&sock_addr).unwrap();
let server_task = listener.incoming().for_each(move |socket| {
let (_writer, reader) = tokio_codec::BytesCodec::new().framed(socket).split();
let read_task = reader.for_each(|bytes| {
/* ... Logic I want to happen repeatedly as bytes are read ... */
});
tokio::spawn(read_task);
Ok(())
}).map_err(|err| {
error!("Accept error = {:?}", err);
});
tokio::run(server_task);