I have found a few posts (1, 2, 3) about using tokio in a daemonized process, but none of them seem to address my workflow.
I'm trying to write a program that does the following:
- Listens on a privileged port
- drops privs
- accepts connections
The problem, as I understand it, is that tokio uses some global state that does not survive the fork that occurs in the daemonizing process. Is there a way around this?
I tried creating 2 runtimes, and breaking the code apart into
bind after the forks, but before changing uid, and
accept after changing uid. I share a
TcpListener between the two functions, however, I get the following error:
Error accepting connection: A Tokio 1.x context was found, but it is being shutdown.
I'm assuming this is because the Tokio context is stored in the
TcpListener somehow, and so I cannot jump between the runtimes like that.
So is there a way to run a program that listens on a privileged port, daemonizes/forks, drops privs, and then accepts connections using tokio?
Complete code (that gives the error above) here: main.rs · GitHub
What about forking first to daemonize and only then listening before dropping privileges? Alternatively if you are fine with limiting yourself to systemd, you could use a socket unit to make systemd listen for you and be able to start without privileges entirely, instead getting an fd for the listener socket from systemd. When using systemd you also doesn't need to daemonize. In fact systemd prefers it if you don't.
What about forking first to daemonize and only then listening before dropping privileges?
This is what I tried to do, but admittedly the Daemonize crate might be getting in my way, and I'd just have to do it "manually" so I keep the same runtime throughout.
you could use a socket unit to make systemd listen for you and be able to start without privileges entirely, instead getting an fd for the listener socket from systemd
At this point, I'm leaning heavily towards just making this a "normal" proc run by whatever user executes it (aka, no daemonizing), and using systemd. However, I have no idea how systemd would hand my proc an fd that is already bound, and which I could "inject" into tokio's
TcpListener... ideas on how to pull that off?
You can use the listenfd crate. This crate implements the sd_listen_fds protocol used by systemd to pass the tcp socket to services. You can do
ListenFd::from_env().take_tcp_listener() and then use
tokio::TcpListener::try_from() to convert the
std::net::TcpListener into a
tokio::TcpListener. For development the author of listenfd recommends using their systemfd crate to use the same protocol outsidr of the context of systemd.
Generally, I recommend that you fork before starting the runtime.
Yea, I saw this in another post... I am doing that, but I'm setting up 2 runtimes, and that's causing me issues. I should, instead, just setup one and share it across the two calls: