Skip to content

UnixStream doesn't set MSG_NOSIGNAL as UnixDatagram #139956

Open
@mlowicki

Description

@mlowicki

UnixDatagram sets MSG_NOSIGNAL when sending data (https://doc.rust-lang.org/src/std/os/unix/net/datagram.rs.html#516)

UnixStream doesn't have it.

Why it's important?

https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html:

[EPIPE]
The socket is shut down for writing, or the socket is connection-mode and is no longer connected. In the latter case, and if the socket is of type SOCK_STREAM or SOCK_SEQPACKET and the MSG_NOSIGNAL flag is not set, the SIGPIPE signal is generated to the calling thread.

So without MSG_NOSIGNAL writing to e.g. closed socket leads to SIGPIPE.

https://pkg.go.dev/os/signal#hdr-Go_programs_that_use_cgo_or_SWIG

If a SIGPIPE signal is received, the Go program will invoke the special handling described above if the SIGPIPE is received on a Go thread. If the SIGPIPE is received on a non-Go thread the signal will be forwarded to the non-Go handler, if any; if there is none the default system handler will cause the program to terminate.

It means Golang programs using Rust library terminate when underlying Rust library doesn't handle disconnected sockets properly (e.g. keep using closed socket).

Discovered problem with 1.83.0 but code for UnixStream doesn't seem to ever pass MSG_NOSIGNAL (so rather not a regression or so).

Apparently old behaviour from #62569 doesn't help neither as Golang runtime doesn't find non-Go signal handler when Rust lib is being used.

MSG_NOSIGNAL doesn't exist everywhere (e.g. not on macOS) so there we could use SO_NOSIGPIPE on a socket but it seems it's already done inside:

pub fn new_raw(fam: c_int, ty: c_int) -> io::Result<Socket> {
unsafe {
cfg_if::cfg_if! {
if #[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "illumos",
target_os = "hurd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd",
target_os = "cygwin",
target_os = "nto",
target_os = "solaris",
))] {
// On platforms that support it we pass the SOCK_CLOEXEC
// flag to atomically create the socket and set it as
// CLOEXEC. On Linux this was added in 2.6.27.
let fd = cvt(libc::socket(fam, ty | libc::SOCK_CLOEXEC, 0))?;
let socket = Socket(FileDesc::from_raw_fd(fd));
// DragonFlyBSD, FreeBSD and NetBSD use `SO_NOSIGPIPE` as a `setsockopt`
// flag to disable `SIGPIPE` emission on socket.
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "dragonfly"))]
setsockopt(&socket, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1)?;
Ok(socket)
} else {
let fd = cvt(libc::socket(fam, ty, 0))?;
let fd = FileDesc::from_raw_fd(fd);
fd.set_cloexec()?;
let socket = Socket(fd);
// macOS and iOS use `SO_NOSIGPIPE` as a `setsockopt`
// flag to disable `SIGPIPE` emission on socket.
#[cfg(target_vendor = "apple")]
setsockopt(&socket, libc::SOL_SOCKET, libc::SO_NOSIGPIPE, 1)?;
Ok(socket)

Passing MSG_NOSIGNAL is handled for other types:

For UnixStream it's pure write on underlying file descriptor:

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ioArea: `std::io`, `std::fs`, `std::net` and `std::path`C-bugCategory: This is a bug.T-libsRelevant to the library team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions