How to Set the Socket Option So_Reuseport in Rust

How to set the socket option SO_REUSEPORT in Rust?

Because SO_REUSEPORT isn't cross-platform, you will need to dip into platform-specific code. In this case, you can get the raw file descriptor from the socket and then use functions, types, and values from the libc crate to set the options you want:

extern crate libc; // 0.2.43

use std::{io, mem, net::TcpListener, os::unix::io::AsRawFd};

fn main() -> Result<(), io::Error> {
let listener = TcpListener::bind("0.0.0.0:8888")?;

unsafe {
let optval: libc::c_int = 1;
let ret = libc::setsockopt(
listener.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_REUSEPORT,
&optval as *const _ as *const libc::c_void,
mem::size_of_val(&optval) as libc::socklen_t,
);
if ret != 0 {
return Err(io::Error::last_os_error());
}
}

Ok(())
}

I make no guarantee that this is the right place to set this option, or that I haven't screwed up something in the unsafe block, but it does compile and run on macOS 10.12.

A better solution may be to check out the nix crate, which provides nicer wrappers for most *nix-specific code:

extern crate nix; // 0.11.0

use nix::sys::socket::{self, sockopt::ReusePort};
use std::{error::Error, net::TcpListener, os::unix::io::AsRawFd};

fn main() -> Result<(), Box<Error>> {
let listener = TcpListener::bind("0.0.0.0:8888")?;
socket::setsockopt(listener.as_raw_fd(), ReusePort, &true)?;

Ok(())
}

An even better solution may be to check out the net2 crate, which provides higher-level methods aimed specifically at networking-related code:

extern crate net2; // 0.2.33

use net2::{unix::UnixTcpBuilderExt, TcpBuilder};

fn main() -> Result<(), std::io::Error> {
let listener = TcpBuilder::new_v4()?
.reuse_address(true)?
.reuse_port(true)?
.bind("0.0.0.0:8888")?
.listen(42)?;

Ok(())
}

Sockets in Rust

The std::net exists for working with sockets.

How do I close a Unix socket in Rust?

Please learn to create a minimal reproducible example and then take the time to do so. In this case, there's no need for threads or functions or testing frameworks; running this entire program twice reproduces the error:

use std::os::unix::net::UnixListener;

fn main() {
UnixListener::bind("/tmp/my_socket.sock").unwrap();
}

If you look at the filesystem before and after the test, you will see that the file /tmp/my_socket.sock is not present before the first run and it is present before the second run. Deleting the file allows the program to run to completion again (at which point it recreates the file).

This issue is not unique to Rust:

Note that, once created, this socket file will continue to exist, even after the server exits. If the server subsequently restarts, the file prevents re-binding:

[...]

So, servers should unlink the socket pathname prior to binding it.

You could choose to add some wrapper around the socket that would automatically delete it when it is dropped or create a temporary directory that is cleaned when it is dropped, but I'm not sure how well that would work. You could also create a wrapper function that deletes the file before it opens the socket.

Unlinking the socket when it's dropped

use std::path::{Path, PathBuf};

struct DeleteOnDrop {
path: PathBuf,
listener: UnixListener,
}

impl DeleteOnDrop {
fn bind(path: impl AsRef<Path>) -> std::io::Result<Self> {
let path = path.as_ref().to_owned();
UnixListener::bind(&path).map(|listener| DeleteOnDrop { path, listener })
}
}

impl Drop for DeleteOnDrop {
fn drop(&mut self) {
// There's no way to return a useful error here
let _ = std::fs::remove_file(&self.path).unwrap();
}
}

You may also want to consider implementing Deref / DerefMut to make this into a smart pointer for sockets:

impl std::ops::Deref for DeleteOnDrop {
type Target = UnixListener;

fn deref(&self) -> &Self::Target {
&self.listener
}
}

impl std::ops::DerefMut for DeleteOnDrop {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.listener
}
}

Unlinking the socket before it's opened

This is much simpler:

use std::path::Path;

fn bind(path: impl AsRef<Path>) -> std::io::Result<UnixListener> {
let path = path.as_ref();
std::fs::remove_file(path)?;
UnixListener::bind(path)
}

Note that you can combine the two solutions, such that the socket is deleted before creation and when it's dropped.

I think that deleting during creation is a less-optimal solution: if you ever start a second server, you'll prevent the first server from receiving any more connections. It's probably better to error and tell the user instead.



Related Topics



Leave a reply



Submit