Support SOCK_SEQPACKET

This commit is contained in:
Ruihan Li 2025-06-25 23:55:42 +08:00 committed by Tate, Hongliang Tian
parent c3572e9548
commit 5ccadb6253
16 changed files with 437 additions and 234 deletions

View File

@ -99,8 +99,10 @@ impl Connected {
pub(super) fn try_read(
&self,
writer: &mut dyn MultiWrite,
is_seqpacket: bool,
) -> Result<(usize, Vec<ControlMessage>)> {
if writer.is_empty() {
let is_empty = writer.is_empty();
if is_empty && !is_seqpacket {
if self.reader.lock().is_empty() {
return_errno_with_message!(Errno::EAGAIN, "the channel is empty");
}
@ -158,9 +160,12 @@ impl Connected {
}
// Read the payload bytes of the current auxiliary data.
let read_res = self
.inner
.read_with(|| reader.read_fallible_with_max_len(writer, aux_len));
let read_res = if !is_empty && aux_len > 0 {
self.inner
.read_with(|| reader.read_fallible_with_max_len(writer, aux_len))
} else {
Ok(0)
};
let read_len = match read_res {
Ok(read_len) => read_len,
Err(_) if read_tot_len > 0 => break aux_prev_data.as_mut().unwrap(),
@ -168,8 +173,16 @@ impl Connected {
};
read_tot_len += read_len;
// Record the current auxiliary data. Break if the read is incomplete.
if let Some(front) = aux_front {
// Record the current auxiliary data. Break if the read is incomplete or this is a
// `SOCK_SEQPACKET` socket.
if is_seqpacket {
aux_prev_data = Some(all_aux.pop_front().unwrap().data);
if read_len < aux_len {
warn!("setting MSG_TRUNC is not supported");
reader.skip(aux_len - read_len);
}
break aux_prev_data.as_mut().unwrap();
} else if let Some(front) = aux_front {
if read_len < aux_len {
front.start += read_len;
break &mut front.data;
@ -186,7 +199,7 @@ impl Connected {
drop(reader);
let ctrl_msgs = aux_data.generate_control(is_pass_cred);
debug_assert_ne!(read_tot_len, 0);
debug_assert!(is_empty || read_tot_len != 0);
peer_end
.has_aux
.store(!all_aux.is_empty(), Ordering::Relaxed);
@ -198,12 +211,20 @@ impl Connected {
&self,
reader: &mut dyn MultiRead,
aux_data: &mut AuxiliaryData,
is_seqpacket: bool,
) -> Result<usize> {
if reader.is_empty() {
let is_empty = reader.is_empty();
if is_empty {
if self.inner.is_shutdown() {
return_errno_with_message!(Errno::EPIPE, "the channel is shut down");
}
return Ok(0);
if !is_seqpacket {
return Ok(0);
}
}
if is_seqpacket && reader.sum_lens() >= UNIX_STREAM_DEFAULT_BUF_SIZE {
return_errno_with_message!(Errno::EMSGSIZE, "the message is too large");
}
let this_end = self.inner.this_end();
@ -211,9 +232,14 @@ impl Connected {
|| self.inner.peer_end().is_pass_cred.load(Ordering::Relaxed);
// Fast path: There are no auxiliary data to transmit.
if aux_data.is_empty() && !need_pass_cred {
if aux_data.is_empty() && !is_seqpacket && !need_pass_cred {
let mut writer = self.writer.lock();
return self.inner.write_with(move || writer.write_fallible(reader));
return self.inner.write_with(move || {
if is_seqpacket && writer.free_len() < reader.sum_lens() {
return Ok(0);
}
writer.write_fallible(reader)
});
}
let mut all_aux = this_end.all_aux.lock();
@ -223,11 +249,18 @@ impl Connected {
this_end.has_aux.store(true, Ordering::Relaxed);
// Write the payload bytes.
let (write_start, write_res) = {
let (write_start, write_res) = if !is_empty {
let mut writer = self.writer.lock();
let write_start = writer.tail();
let write_res = self.inner.write_with(move || writer.write_fallible(reader));
let write_res = self.inner.write_with(move || {
if is_seqpacket && writer.free_len() < reader.sum_lens() {
return Ok(0);
}
writer.write_fallible(reader)
});
(write_start, write_res)
} else {
(self.writer.lock().tail(), Ok(0))
};
let Ok(write_len) = write_res else {
this_end

View File

@ -82,6 +82,7 @@ impl Init {
self,
backlog: usize,
pollee: Pollee,
is_seqpacket: bool,
) -> core::result::Result<Listener, (Error, Self)> {
let Some(addr) = self.addr else {
return Err((
@ -98,6 +99,7 @@ impl Init {
self.is_read_shutdown.into_inner(),
self.is_write_shutdown.into_inner(),
pollee,
is_seqpacket,
))
}

View File

@ -38,9 +38,10 @@ impl Listener {
is_read_shutdown: bool,
is_write_shutdown: bool,
pollee: Pollee,
is_seqpacket: bool,
) -> Self {
let backlog = BACKLOG_TABLE
.add_backlog(addr, pollee, backlog, is_read_shutdown)
.add_backlog(addr, pollee, backlog, is_read_shutdown, is_seqpacket)
.unwrap();
Self {
@ -53,13 +54,13 @@ impl Listener {
self.backlog.addr()
}
pub(super) fn try_accept(&self) -> Result<(Arc<dyn FileLike>, SocketAddr)> {
pub(super) fn try_accept(&self, is_seqpacket: bool) -> Result<(Arc<dyn FileLike>, SocketAddr)> {
let connected = self.backlog.pop_incoming()?;
let peer_addr = connected.peer_addr().into();
// TODO: Update options for a newly-accepted socket
let options = OptionSet::new();
let socket = UnixStreamSocket::new_connected(connected, options, false);
let socket = UnixStreamSocket::new_connected(connected, options, false, is_seqpacket);
Ok((socket, peer_addr))
}
@ -123,6 +124,7 @@ impl BacklogTable {
pollee: Pollee,
backlog: usize,
is_shutdown: bool,
is_seqpacket: bool,
) -> Option<Arc<Backlog>> {
let addr_key = addr.to_key();
@ -132,7 +134,13 @@ impl BacklogTable {
return None;
}
let new_backlog = Arc::new(Backlog::new(addr, pollee, backlog, is_shutdown));
let new_backlog = Arc::new(Backlog::new(
addr,
pollee,
backlog,
is_shutdown,
is_seqpacket,
));
backlog_sockets.insert(addr_key, new_backlog.clone());
Some(new_backlog)
@ -154,10 +162,17 @@ pub(super) struct Backlog {
incoming_conns: SpinLock<Option<VecDeque<Connected>>>,
wait_queue: WaitQueue,
listener_cred: SocketCred<ReadDupOp>,
is_seqpacket: bool,
}
impl Backlog {
fn new(addr: UnixSocketAddrBound, pollee: Pollee, backlog: usize, is_shutdown: bool) -> Self {
fn new(
addr: UnixSocketAddrBound,
pollee: Pollee,
backlog: usize,
is_shutdown: bool,
is_seqpacket: bool,
) -> Self {
let incoming_sockets = if is_shutdown {
None
} else {
@ -171,6 +186,7 @@ impl Backlog {
incoming_conns: SpinLock::new(incoming_sockets),
wait_queue: WaitQueue::new(),
listener_cred: SocketCred::<ReadDupOp>::new_current(),
is_seqpacket,
}
}
@ -235,7 +251,21 @@ impl Backlog {
init: Init,
pollee: Pollee,
options: &SocketOptionSet,
is_seqpacket: bool,
) -> core::result::Result<Connected, (Error, Init)> {
if is_seqpacket != self.is_seqpacket {
// FIXME: According to the Linux implementation, we should avoid this error by
// maintaining two socket tables for SOCK_STREAM sockets and SOCK_SEQPACKET sockets
// separately.
return Err((
Error::with_message(
Errno::ECONNREFUSED,
"the listening socket has a different socket type",
),
init,
));
}
let mut locked_incoming_conns = self.incoming_conns.lock();
let Some(incoming_conns) = &mut *locked_incoming_conns else {

View File

@ -39,15 +39,18 @@ pub struct UnixStreamSocket {
pollee: Pollee,
is_nonblocking: AtomicBool,
is_seqpacket: bool,
}
impl UnixStreamSocket {
pub(super) fn new_init(init: Init, is_nonblocking: bool) -> Arc<Self> {
pub(super) fn new_init(init: Init, is_nonblocking: bool, is_seqpacket: bool) -> Arc<Self> {
Arc::new(Self {
state: RwMutex::new(Takeable::new(State::Init(init))),
options: RwMutex::new(OptionSet::new()),
pollee: Pollee::new(),
is_nonblocking: AtomicBool::new(is_nonblocking),
is_seqpacket,
})
}
@ -55,6 +58,7 @@ impl UnixStreamSocket {
connected: Connected,
options: OptionSet,
is_nonblocking: bool,
is_seqpacket: bool,
) -> Arc<Self> {
let cloned_pollee = connected.cloned_pollee();
Arc::new(Self {
@ -62,6 +66,7 @@ impl UnixStreamSocket {
options: RwMutex::new(options),
pollee: cloned_pollee,
is_nonblocking: AtomicBool::new(is_nonblocking),
is_seqpacket,
})
}
}
@ -155,11 +160,11 @@ impl OptionSet {
}
impl UnixStreamSocket {
pub fn new(is_nonblocking: bool) -> Arc<Self> {
Self::new_init(Init::new(), is_nonblocking)
pub fn new(is_nonblocking: bool, is_seqpacket: bool) -> Arc<Self> {
Self::new_init(Init::new(), is_nonblocking, is_seqpacket)
}
pub fn new_pair(is_nonblocking: bool) -> (Arc<Self>, Arc<Self>) {
pub fn new_pair(is_nonblocking: bool, is_seqpacket: bool) -> (Arc<Self>, Arc<Self>) {
let cred = SocketCred::<ReadDupOp>::new_current();
let options = OptionSet::new();
@ -173,8 +178,8 @@ impl UnixStreamSocket {
&options.socket,
);
(
Self::new_connected(conn_a, options, is_nonblocking),
Self::new_connected(conn_b, OptionSet::new(), is_nonblocking),
Self::new_connected(conn_a, options, is_nonblocking, is_seqpacket),
Self::new_connected(conn_b, OptionSet::new(), is_nonblocking, is_seqpacket),
)
}
@ -185,7 +190,7 @@ impl UnixStreamSocket {
_flags: SendRecvFlags,
) -> Result<usize> {
match self.state.read().as_ref() {
State::Connected(connected) => connected.try_write(buf, aux_data),
State::Connected(connected) => connected.try_write(buf, aux_data, self.is_seqpacket),
State::Init(_) | State::Listen(_) => {
return_errno_with_message!(Errno::ENOTCONN, "the socket is not connected")
}
@ -198,7 +203,7 @@ impl UnixStreamSocket {
_flags: SendRecvFlags,
) -> Result<(usize, Vec<ControlMessage>)> {
match self.state.read().as_ref() {
State::Connected(connected) => connected.try_read(buf),
State::Connected(connected) => connected.try_read(buf, self.is_seqpacket),
State::Init(_) | State::Listen(_) => {
return_errno_with_message!(Errno::EINVAL, "the socket is not connected")
}
@ -232,8 +237,12 @@ impl UnixStreamSocket {
}
};
let connected = match backlog.push_incoming(init, self.pollee.clone(), &options.socket)
{
let connected = match backlog.push_incoming(
init,
self.pollee.clone(),
&options.socket,
self.is_seqpacket,
) {
Ok(connected) => connected,
Err((err, init)) => return (State::Init(init), Err(err)),
};
@ -244,7 +253,7 @@ impl UnixStreamSocket {
fn try_accept(&self) -> Result<(Arc<dyn FileLike>, SocketAddr)> {
match self.state.read().as_ref() {
State::Listen(listen) => listen.try_accept() as _,
State::Listen(listen) => listen.try_accept(self.is_seqpacket) as _,
State::Init(_) | State::Connected(_) => {
return_errno_with_message!(Errno::EINVAL, "the socket is not listening")
}
@ -326,7 +335,7 @@ impl Socket for UnixStreamSocket {
}
};
let listener = match init.listen(backlog, self.pollee.clone()) {
let listener = match init.listen(backlog, self.pollee.clone(), self.is_seqpacket) {
Ok(listener) => listener,
Err((err, init)) => {
return (State::Init(init), Err(err));
@ -391,7 +400,7 @@ impl Socket for UnixStreamSocket {
// According to the Linux man pages, `EISCONN` _may_ be returned when the destination
// address is specified for a connection-mode socket. In practice, `sendmsg` on UNIX stream
// sockets will fail due to that. We follow the same behavior as the Linux implementation.
if addr.is_some() {
if !self.is_seqpacket && addr.is_some() {
match self.state.read().as_ref() {
State::Init(_) | State::Listen(_) => return_errno_with_message!(
Errno::EOPNOTSUPP,

View File

@ -23,11 +23,14 @@ pub fn sys_socket(domain: i32, type_: i32, protocol: i32, ctx: &Context) -> Resu
"domain = {:?}, sock_type = {:?}, sock_flags = {:?}",
domain, sock_type, sock_flags
);
let is_nonblocking = sock_flags.contains(SockFlags::SOCK_NONBLOCK);
let file_like = match (domain, sock_type) {
// FIXME: SOCK_SEQPACKET is added to run fcntl_test, not supported yet.
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_STREAM | SockType::SOCK_SEQPACKET) => {
UnixStreamSocket::new(is_nonblocking) as Arc<dyn FileLike>
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_STREAM) => {
UnixStreamSocket::new(is_nonblocking, false) as Arc<dyn FileLike>
}
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_SEQPACKET) => {
UnixStreamSocket::new(is_nonblocking, true) as Arc<dyn FileLike>
}
(CSocketAddrFamily::AF_INET, SockType::SOCK_STREAM) => {
let protocol = Protocol::try_from(protocol)?;
@ -81,6 +84,7 @@ pub fn sys_socket(domain: i32, type_: i32, protocol: i32, ctx: &Context) -> Resu
}
_ => return_errno_with_message!(Errno::EAFNOSUPPORT, "unsupported domain"),
};
let fd = {
let file_table = ctx.thread_local.borrow_file_table();
let mut file_table_locked = file_table.unwrap().write();
@ -91,5 +95,6 @@ pub fn sys_socket(domain: i32, type_: i32, protocol: i32, ctx: &Context) -> Resu
};
file_table_locked.insert(file_like, fd_flags)
};
Ok(SyscallReturn::Return(fd as _))
}

View File

@ -19,16 +19,19 @@ pub fn sys_socketpair(
let sock_type = SockType::try_from(type_ & SOCK_TYPE_MASK)?;
let sock_flags = SockFlags::from_bits_truncate(type_ & !SOCK_TYPE_MASK);
let protocol = Protocol::try_from(protocol)?;
debug!(
"domain = {:?}, sock_type = {:?}, sock_flags = {:?}, protocol = {:?}",
domain, sock_type, sock_flags, protocol
);
// TODO: deal with all sock_flags and protocol
let nonblocking = sock_flags.contains(SockFlags::SOCK_NONBLOCK);
let (socket_a, socket_b) = match (domain, sock_type) {
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_STREAM) => {
UnixStreamSocket::new_pair(nonblocking)
UnixStreamSocket::new_pair(nonblocking, false)
}
(CSocketAddrFamily::AF_UNIX, SockType::SOCK_SEQPACKET) => {
UnixStreamSocket::new_pair(nonblocking, true)
}
_ => return_errno_with_message!(
Errno::EAFNOSUPPORT,
@ -48,8 +51,8 @@ pub fn sys_socketpair(
let fd_b = file_table_locked.insert(socket_b, fd_flags);
SocketFds(fd_a, fd_b)
};
ctx.user_space().write_val(sv, &socket_fds)?;
Ok(SyscallReturn::Return(0))
}

View File

@ -346,6 +346,8 @@ impl<R: Deref<Target = RingBuffer<u8>>> Producer<u8, R> {
rb.advance_tail(tail, write_len);
Ok(write_len)
}
// There is no counterpart to `Consumer::skip`. It does not make sense for the producer.
}
#[inherit_methods(from = "self.rb")]
@ -460,6 +462,22 @@ impl<R: Deref<Target = RingBuffer<u8>>> Consumer<u8, R> {
rb.advance_head(head, read_len);
Ok(read_len)
}
/// Skips `count` bytes in the `RingBuffer`.
///
/// In other words, `count` bytes are read from the `RingBuffer` and discarded.
///
/// # Panics
///
/// This method will panic if the number of the available bytes to read is less than `count`.
pub fn skip(&mut self, count: usize) {
let rb = &self.rb;
let len = rb.len();
assert!(len >= count);
let head = rb.head();
rb.advance_head(head, count);
}
}
#[inherit_methods(from = "self.rb")]

View File

@ -0,0 +1,67 @@
// SPDX-License-Identifier: MPL-2.0
#define SOCK_TYPE SOCK_SEQPACKET
#include "unix_streamlike_prologue.h"
FN_TEST(sendto)
{
char buf[1] = { 'z' };
TEST_ERRNO(sendto(sk_unbound, buf, 1, 0, &LISTEN_ADDR, LISTEN_ADDRLEN),
ENOTCONN);
TEST_ERRNO(sendto(sk_bound, buf, 1, 0, &LISTEN_ADDR2, LISTEN_ADDRLEN2),
ENOTCONN);
TEST_ERRNO(sendto(sk_listen, buf, 1, 0, &BOUND_ADDR, BOUND_ADDRLEN),
ENOTCONN);
}
END_TEST()
FN_TEST(send_recv_trunc)
{
int fildes[2];
char buf[1];
TEST_SUCC(
socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, fildes));
TEST_SUCC(send(fildes[0], "abc", 3, 0));
TEST_SUCC(send(fildes[0], "def", 3, 0));
TEST_SUCC(send(fildes[0], "hij", 3, 0));
TEST_RES(recv(fildes[1], buf, 1, 0), _ret == 1 && buf[0] == 'a');
TEST_RES(recv(fildes[1], buf, 0, 0), _ret == 0);
TEST_RES(recv(fildes[1], buf, 1, 0), _ret == 1 && buf[0] == 'h');
TEST_SUCC(close(fildes[0]));
TEST_SUCC(close(fildes[1]));
}
END_TEST()
FN_TEST(send_recv_zero)
{
int fildes[2];
char buf[1];
TEST_SUCC(
socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, fildes));
buf[0] = 'a';
TEST_SUCC(send(fildes[0], buf, 1, 0));
buf[0] = 'b';
TEST_SUCC(send(fildes[0], buf, 0, 0));
buf[0] = 'c';
TEST_SUCC(send(fildes[0], buf, 0, 0));
buf[0] = 'd';
TEST_SUCC(send(fildes[0], buf, 1, 0));
TEST_RES(recv(fildes[1], buf, 1, 0), _ret == 1 && buf[0] == 'a');
TEST_RES(recv(fildes[1], buf, 1, 0), _ret == 0 && buf[0] == 'a');
TEST_RES(recv(fildes[1], buf, 1, 0), _ret == 0 && buf[0] == 'a');
TEST_RES(recv(fildes[1], buf, 1, 0), _ret == 1 && buf[0] == 'd');
TEST_SUCC(close(fildes[0]));
TEST_SUCC(close(fildes[1]));
}
END_TEST()
#include "unix_streamlike_epilogue.h"

View File

@ -0,0 +1,117 @@
// SPDX-License-Identifier: MPL-2.0
#define SOCK_TYPE SOCK_STREAM
#include "unix_streamlike_prologue.h"
FN_TEST(sendto)
{
char buf[1] = { 'z' };
TEST_ERRNO(sendto(sk_unbound, buf, 1, 0, &LISTEN_ADDR, LISTEN_ADDRLEN),
EOPNOTSUPP);
TEST_ERRNO(sendto(sk_bound, buf, 1, 0, &LISTEN_ADDR2, LISTEN_ADDRLEN2),
EOPNOTSUPP);
TEST_ERRNO(sendto(sk_listen, buf, 1, 0, &BOUND_ADDR, BOUND_ADDRLEN),
EOPNOTSUPP);
TEST_ERRNO(sendto(sk_accepted, buf, 1, 0, &UNNAMED_ADDR,
UNNAMED_ADDRLEN),
EISCONN);
}
END_TEST()
FN_TEST(scm_rights)
{
int fildes[2];
char buf[20] = "abcdefg";
char cbuf[CMSG_SPACE(sizeof(int) * 3)];
struct iovec iov;
struct msghdr mhdr;
struct cmsghdr *chdr;
int *cdata;
int cfds[2];
TEST_SUCC(socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fildes));
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cbuf;
mhdr.msg_controllen = CMSG_SPACE(sizeof(int) * 3);
iov.iov_base = buf;
iov.iov_len = 1;
chdr = CMSG_FIRSTHDR(&mhdr);
chdr->cmsg_level = SOL_SOCKET;
chdr->cmsg_type = SCM_RIGHTS;
chdr->cmsg_len = CMSG_SPACE(sizeof(int) * 3);
cdata = (int *)CMSG_DATA(chdr);
TEST_SUCC(pipe(cfds));
cdata[0] = cfds[0];
cdata[1] = cfds[0];
cdata[2] = cfds[1];
// Sending control messages with zero bytes to a stream socket
// seems to "succeed". However, no data or control messages can
// be transmitted.
mhdr.msg_iovlen = 0;
TEST_SUCC(sendmsg(fildes[0], &mhdr, 0));
mhdr.msg_iovlen = 1;
// > (1) sendmsg(2) of four bytes, with no ancillary data.
// > (2) sendmsg(2) of one byte, with ancillary data.
// > (3) sendmsg(2) of four bytes, with no ancillary data.
// -- https://man7.org/linux/man-pages/man7/unix.7.html
TEST_RES(send(fildes[0], buf, 4, 0), _ret == 4);
TEST_RES(sendmsg(fildes[0], &mhdr, 0), _ret == 1);
TEST_RES(send(fildes[0], buf, 4, 0), _ret == 4);
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cbuf;
mhdr.msg_controllen = CMSG_SPACE(sizeof(int));
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
memset(cbuf, 0, sizeof(cbuf));
// > Suppose that the receiver now performs recvmsg(2) calls each with
// > a buffer size of 20 bytes. The first call will receive five bytes
// > of data, along with the ancillary data sent by the second
// > sendmsg(2) call.
TEST_RES(recvmsg(fildes[1], &mhdr, 0),
_ret == 5 &&
mhdr.msg_controllen == CMSG_SPACE(sizeof(int) * 2) &&
(chdr = CMSG_FIRSTHDR(&mhdr)) &&
chdr->cmsg_level == SOL_SOCKET &&
chdr->cmsg_type == SCM_RIGHTS &&
chdr->cmsg_len == CMSG_SPACE(sizeof(int) * 2) &&
(cdata = (int *)CMSG_DATA(chdr)) &&
cdata[0] == cfds[1] + 1 && cdata[1] == cfds[1] + 2);
// > The next call will receive the remaining four
// > bytes of data.
TEST_RES(recv(fildes[1], buf, sizeof(buf), 0), _ret == 4);
// The purpose of the tests below is to verify that the received file
// descriptors are functional.
TEST_RES(write(cfds[1], "x", 1), _ret == 1);
TEST_RES(read(cdata[0], buf, 1), _ret == 1 && buf[0] == 'x');
TEST_RES(write(cfds[1], "y", 1), _ret == 1);
TEST_RES(read(cdata[1], buf, 1), _ret == 1 && buf[0] == 'y');
TEST_SUCC(close(cdata[0]));
TEST_SUCC(close(cdata[1]));
TEST_SUCC(close(cfds[0]));
TEST_ERRNO(write(cfds[1], "y", 1), EPIPE);
TEST_SUCC(close(cfds[1]));
TEST_SUCC(close(fildes[0]));
TEST_SUCC(close(fildes[1]));
}
END_TEST()
#include "unix_streamlike_epilogue.h"

View File

@ -0,0 +1,19 @@
/* SPDX-License-Identifier: MPL-2.0 */
FN_SETUP(cleanup)
{
CHECK(close(sk_unbound));
CHECK(close(sk_bound));
CHECK(close(sk_listen));
CHECK(close(sk_connected));
CHECK(close(sk_accepted));
CHECK(unlink(BOUND_ADDR.sun_path));
CHECK(unlink(LISTEN_ADDR.sun_path));
}
END_SETUP()

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: MPL-2.0
/* SPDX-License-Identifier: MPL-2.0 */
#define _GNU_SOURCE
@ -31,7 +31,7 @@ FN_TEST(socket_addresses)
#define MAKE_TEST(path, path_copy_len, path_len_to_kernel, path_buf_len, \
path_len_from_kernel, path_from_kernel) \
sk = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0)); \
sk = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0)); \
\
memset(&addr, 0, sizeof(addr)); \
addr.sun_family = AF_UNIX; \
@ -86,7 +86,7 @@ FN_TEST(socket_addresses)
#undef LONG_PATH
#undef MAKE_TEST
sk = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
TEST_ERRNO(bind(sk, (struct sockaddr *)&addr, -1), EINVAL);
TEST_ERRNO(bind(sk, (struct sockaddr *)&addr, PATH_OFFSET - 1), EINVAL);
@ -120,13 +120,13 @@ static int sk_accepted;
FN_SETUP(unbound)
{
sk_unbound = CHECK(socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0));
sk_unbound = CHECK(socket(PF_UNIX, SOCK_TYPE | SOCK_NONBLOCK, 0));
}
END_SETUP()
FN_SETUP(bound)
{
sk_bound = CHECK(socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0));
sk_bound = CHECK(socket(PF_UNIX, SOCK_TYPE | SOCK_NONBLOCK, 0));
CHECK(bind(sk_bound, (struct sockaddr *)&BOUND_ADDR, BOUND_ADDRLEN));
}
@ -134,7 +134,7 @@ END_SETUP()
FN_SETUP(listen)
{
sk_listen = CHECK(socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0));
sk_listen = CHECK(socket(PF_UNIX, SOCK_TYPE | SOCK_NONBLOCK, 0));
CHECK(bind(sk_listen, (struct sockaddr *)&LISTEN_ADDR, LISTEN_ADDRLEN));
@ -144,7 +144,7 @@ END_SETUP()
FN_SETUP(connected)
{
sk_connected = CHECK(socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0));
sk_connected = CHECK(socket(PF_UNIX, SOCK_TYPE | SOCK_NONBLOCK, 0));
CHECK(connect(sk_connected, (struct sockaddr *)&LISTEN_ADDR2,
LISTEN_ADDRLEN2));
@ -242,7 +242,7 @@ FN_TEST(bind_connected)
struct sockaddr_un addr;
socklen_t addrlen;
TEST_SUCC(socketpair(PF_UNIX, SOCK_STREAM, 0, fildes));
TEST_SUCC(socketpair(PF_UNIX, SOCK_TYPE, 0, fildes));
TEST_SUCC(bind(fildes[0], (struct sockaddr *)&UNIX_ADDR("\0X"),
PATH_OFFSET + 2));
@ -340,16 +340,6 @@ FN_TEST(send)
TEST_ERRNO(send(sk_listen, buf, 0, 0), ENOTCONN);
TEST_ERRNO(write(sk_listen, buf, 1), ENOTCONN);
TEST_ERRNO(write(sk_listen, buf, 0), ENOTCONN);
TEST_ERRNO(sendto(sk_unbound, buf, 1, 0, &LISTEN_ADDR, LISTEN_ADDRLEN),
EOPNOTSUPP);
TEST_ERRNO(sendto(sk_bound, buf, 1, 0, &LISTEN_ADDR2, LISTEN_ADDRLEN2),
EOPNOTSUPP);
TEST_ERRNO(sendto(sk_listen, buf, 1, 0, &BOUND_ADDR, BOUND_ADDRLEN),
EOPNOTSUPP);
TEST_ERRNO(sendto(sk_accepted, buf, 1, 0, &UNNAMED_ADDR,
UNNAMED_ADDRLEN),
EISCONN);
}
END_TEST()
@ -387,39 +377,39 @@ FN_TEST(blocking_connect)
// Setup
sk = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
TEST_SUCC(
bind(sk, (struct sockaddr *)&UNIX_ADDR("\0"), PATH_OFFSET + 1));
TEST_SUCC(listen(sk, 2));
for (i = 0; i < 3; ++i) {
sks[i] = TEST_SUCC(
socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0));
socket(PF_UNIX, SOCK_TYPE | SOCK_NONBLOCK, 0));
TEST_SUCC(connect(sks[i], (struct sockaddr *)&UNIX_ADDR("\0"),
PATH_OFFSET + 1));
}
#define MAKE_TEST(child, parent, errno) \
sks[i] = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)); \
TEST_ERRNO(connect(sks[i], (struct sockaddr *)&UNIX_ADDR("\0"), \
PATH_OFFSET + 1), \
EAGAIN); \
TEST_SUCC(close(sks[i])); \
\
pid = TEST_SUCC(fork()); \
if (pid == 0) { \
usleep(300 * 1000); \
CHECK(child); \
exit(0); \
} \
TEST_SUCC(parent); \
\
sks[i] = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0)); \
TEST_ERRNO(connect(sks[i], (struct sockaddr *)&UNIX_ADDR("\0"), \
PATH_OFFSET + 1), \
errno); \
\
TEST_SUCC(close(sks[i])); \
#define MAKE_TEST(child, parent, errno) \
sks[i] = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE | SOCK_NONBLOCK, 0)); \
TEST_ERRNO(connect(sks[i], (struct sockaddr *)&UNIX_ADDR("\0"), \
PATH_OFFSET + 1), \
EAGAIN); \
TEST_SUCC(close(sks[i])); \
\
pid = TEST_SUCC(fork()); \
if (pid == 0) { \
usleep(300 * 1000); \
CHECK(child); \
exit(0); \
} \
TEST_SUCC(parent); \
\
sks[i] = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0)); \
TEST_ERRNO(connect(sks[i], (struct sockaddr *)&UNIX_ADDR("\0"), \
PATH_OFFSET + 1), \
errno); \
\
TEST_SUCC(close(sks[i])); \
TEST_SUCC(wait(NULL));
// Test 1: Accepting a connection resumes the blocked connection request
@ -474,14 +464,14 @@ FN_TEST(ns_abs)
struct sockaddr_un addr;
socklen_t addrlen;
sk = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
TEST_SUCC(bind(sk, (struct sockaddr *)&UNIX_ADDR(""), PATH_OFFSET));
addrlen = sizeof(addr);
TEST_RES(getsockname(sk, (struct sockaddr *)&addr, &addrlen),
addrlen == PATH_OFFSET + 6 && addr.sun_path[0] == '\0');
sk2 = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk2 = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
TEST_ERRNO(bind(sk2, (struct sockaddr *)&addr, addrlen), EADDRINUSE);
TEST_ERRNO(connect(sk2, (struct sockaddr *)&addr, addrlen),
@ -492,7 +482,7 @@ FN_TEST(ns_abs)
TEST_SUCC(close(sk));
TEST_SUCC(close(sk2));
sk = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
TEST_ERRNO(connect(sk, (struct sockaddr *)&addr, addrlen),
ECONNREFUSED);
TEST_SUCC(bind(sk, (struct sockaddr *)&addr, addrlen));
@ -504,7 +494,7 @@ FN_TEST(shutdown_connected)
{
int fildes[2];
TEST_SUCC(socketpair(PF_UNIX, SOCK_STREAM, 0, fildes));
TEST_SUCC(socketpair(PF_UNIX, SOCK_TYPE, 0, fildes));
TEST_SUCC(shutdown(fildes[0], SHUT_RD));
TEST_SUCC(shutdown(fildes[0], SHUT_WR));
@ -524,7 +514,7 @@ FN_TEST(poll_unbound)
int sk;
struct pollfd pfd = { .events = POLLIN | POLLOUT | POLLRDHUP };
sk = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
pfd.fd = sk;
TEST_RES(poll(&pfd, 1, 0), pfd.revents == (POLLOUT | POLLHUP));
@ -552,7 +542,7 @@ FN_TEST(poll_listen)
int sk;
struct pollfd pfd = { .events = POLLIN | POLLOUT | POLLRDHUP };
sk = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
pfd.fd = sk;
TEST_SUCC(
@ -577,7 +567,7 @@ FN_TEST(poll_connected_close)
int fildes[2];
struct pollfd pfd = { .events = POLLIN | POLLOUT | POLLRDHUP };
TEST_SUCC(socketpair(PF_UNIX, SOCK_STREAM, 0, fildes));
TEST_SUCC(socketpair(PF_UNIX, SOCK_TYPE, 0, fildes));
pfd.fd = fildes[1];
TEST_RES(poll(&pfd, 1, 0), pfd.revents == POLLOUT);
@ -597,18 +587,18 @@ FN_TEST(poll_connected_shutdown)
int fildes[2];
struct pollfd pfd = { .events = POLLIN | POLLOUT | POLLRDHUP };
#define MAKE_TEST(shut, ev1, ev2) \
TEST_SUCC(socketpair(PF_UNIX, SOCK_STREAM, 0, fildes)); \
\
TEST_SUCC(shutdown(fildes[0], shut)); \
\
pfd.fd = fildes[0]; \
TEST_RES(poll(&pfd, 1, 0), pfd.revents == (ev1)); \
\
pfd.fd = fildes[1]; \
TEST_RES(poll(&pfd, 1, 0), pfd.revents == (ev2)); \
\
TEST_SUCC(close(fildes[0])); \
#define MAKE_TEST(shut, ev1, ev2) \
TEST_SUCC(socketpair(PF_UNIX, SOCK_TYPE, 0, fildes)); \
\
TEST_SUCC(shutdown(fildes[0], shut)); \
\
pfd.fd = fildes[0]; \
TEST_RES(poll(&pfd, 1, 0), pfd.revents == (ev1)); \
\
pfd.fd = fildes[1]; \
TEST_RES(poll(&pfd, 1, 0), pfd.revents == (ev2)); \
\
TEST_SUCC(close(fildes[0])); \
TEST_SUCC(close(fildes[1]));
MAKE_TEST(SHUT_RD, POLLIN | POLLOUT | POLLRDHUP, POLLOUT);
@ -630,8 +620,8 @@ FN_TEST(epoll)
// Setup
sk2_listen = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk2_connected = TEST_SUCC(socket(PF_UNIX, SOCK_STREAM, 0));
sk2_listen = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
sk2_connected = TEST_SUCC(socket(PF_UNIX, SOCK_TYPE, 0));
epfd_listen = TEST_SUCC(epoll_create1(0));
ev.events = EPOLLIN;
@ -677,31 +667,13 @@ FN_TEST(epoll)
}
END_TEST()
FN_SETUP(cleanup)
{
CHECK(close(sk_unbound));
CHECK(close(sk_bound));
CHECK(close(sk_listen));
CHECK(close(sk_connected));
CHECK(close(sk_accepted));
CHECK(unlink(BOUND_ADDR.sun_path));
CHECK(unlink(LISTEN_ADDR.sun_path));
}
END_SETUP()
// See also `zero_reads_always_succeed` in `pipe_err.c`
FN_TEST(zero_recvs_may_fail)
{
int fildes[2];
char buf[1] = { 'z' };
TEST_SUCC(socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fildes));
TEST_SUCC(socketpair(AF_UNIX, SOCK_TYPE | SOCK_NONBLOCK, 0, fildes));
TEST_ERRNO(recv(fildes[0], buf, 0, 0), EAGAIN);
@ -719,7 +691,7 @@ FN_TEST(zero_sends_may_fail)
int fildes[2];
char buf[1] = { 'z' };
TEST_SUCC(socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fildes));
TEST_SUCC(socketpair(AF_UNIX, SOCK_TYPE | SOCK_NONBLOCK, 0, fildes));
TEST_SUCC(send(fildes[1], buf, 0, 0));
@ -729,98 +701,3 @@ FN_TEST(zero_sends_may_fail)
TEST_SUCC(close(fildes[1]));
}
END_TEST()
FN_TEST(scm_rights)
{
int fildes[2];
char buf[20] = "abcdefg";
char cbuf[CMSG_SPACE(sizeof(int) * 3)];
struct iovec iov;
struct msghdr mhdr;
struct cmsghdr *chdr;
int *cdata;
int cfds[2];
TEST_SUCC(socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fildes));
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cbuf;
mhdr.msg_controllen = CMSG_SPACE(sizeof(int) * 3);
iov.iov_base = buf;
iov.iov_len = 1;
chdr = CMSG_FIRSTHDR(&mhdr);
chdr->cmsg_level = SOL_SOCKET;
chdr->cmsg_type = SCM_RIGHTS;
chdr->cmsg_len = CMSG_SPACE(sizeof(int) * 3);
cdata = (int *)CMSG_DATA(chdr);
TEST_SUCC(pipe(cfds));
cdata[0] = cfds[0];
cdata[1] = cfds[0];
cdata[2] = cfds[1];
// Sending control messages with zero bytes to a stream socket
// seems to "succeed". However, no data or control messages can
// be transmitted.
mhdr.msg_iovlen = 0;
TEST_SUCC(sendmsg(fildes[0], &mhdr, 0));
mhdr.msg_iovlen = 1;
// > (1) sendmsg(2) of four bytes, with no ancillary data.
// > (2) sendmsg(2) of one byte, with ancillary data.
// > (3) sendmsg(2) of four bytes, with no ancillary data.
// -- https://man7.org/linux/man-pages/man7/unix.7.html
TEST_RES(send(fildes[0], buf, 4, 0), _ret == 4);
TEST_RES(sendmsg(fildes[0], &mhdr, 0), _ret == 1);
TEST_RES(send(fildes[0], buf, 4, 0), _ret == 4);
memset(&mhdr, 0, sizeof(mhdr));
mhdr.msg_iov = &iov;
mhdr.msg_iovlen = 1;
mhdr.msg_control = cbuf;
mhdr.msg_controllen = CMSG_SPACE(sizeof(int));
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
memset(cbuf, 0, sizeof(cbuf));
// > Suppose that the receiver now performs recvmsg(2) calls each with
// > a buffer size of 20 bytes. The first call will receive five bytes
// > of data, along with the ancillary data sent by the second
// > sendmsg(2) call.
TEST_RES(recvmsg(fildes[1], &mhdr, 0),
_ret == 5 &&
mhdr.msg_controllen == CMSG_SPACE(sizeof(int) * 2) &&
(chdr = CMSG_FIRSTHDR(&mhdr)) &&
chdr->cmsg_level == SOL_SOCKET &&
chdr->cmsg_type == SCM_RIGHTS &&
chdr->cmsg_len == CMSG_SPACE(sizeof(int) * 2) &&
(cdata = (int *)CMSG_DATA(chdr)) &&
cdata[0] == cfds[1] + 1 && cdata[1] == cfds[1] + 2);
// > The next call will receive the remaining four
// > bytes of data.
TEST_RES(recv(fildes[1], buf, sizeof(buf), 0), _ret == 4);
// The purpose of the tests below is to verify that the received file
// descriptors are functional.
TEST_RES(write(cfds[1], "x", 1), _ret == 1);
TEST_RES(read(cdata[0], buf, 1), _ret == 1 && buf[0] == 'x');
TEST_RES(write(cfds[1], "y", 1), _ret == 1);
TEST_RES(read(cdata[1], buf, 1), _ret == 1 && buf[0] == 'y');
TEST_SUCC(close(cdata[0]));
TEST_SUCC(close(cdata[1]));
TEST_SUCC(close(cfds[0]));
TEST_ERRNO(write(cfds[1], "y", 1), EPIPE);
TEST_SUCC(close(cfds[1]));
TEST_SUCC(close(fildes[0]));
TEST_SUCC(close(fildes[1]));
}
END_TEST()

View File

@ -33,7 +33,8 @@ sleep 0.2
./tcp_err
./tcp_poll
./udp_err
./unix_err
./unix_stream_err
./unix_seqpacket_err
./netlink_route
./rtnl_err

View File

@ -37,10 +37,11 @@
#include <string.h>
/** Starts the definition of a setup function. */
#define FN_SETUP(name) \
void setup_##name(void) __attribute__((constructor(__LINE__ + 200))); \
\
void setup_##name(void) \
#define FN_SETUP(name) \
void setup_##name(void) \
__attribute__((constructor(__COUNTER__ + 200))); \
\
void setup_##name(void) \
{
/** Ends the definition of a setup function. */
#define END_SETUP() }
@ -84,11 +85,12 @@
static int __total_failures;
/** Starts the definition of a test function. */
#define FN_TEST(name) \
void test_##name(void) __attribute__((constructor(__LINE__ + 200))); \
\
void test_##name(void) \
{ \
#define FN_TEST(name) \
void test_##name(void) \
__attribute__((constructor(__COUNTER__ + 200))); \
\
void test_##name(void) \
{ \
int __tests_passed = 0, __tests_failed = 0;
/** Ends the definition of a test function. */

View File

@ -51,7 +51,10 @@ TESTS ?= \
signalfd_test \
socket_netlink_route_test \
socket_unix_pair_test \
socket_unix_seqpacket_local_test \
socket_unix_stream_test \
socket_unix_unbound_seqpacket_test \
socket_unix_unbound_stream_test \
stat_test \
stat_times_test \
statfs_test \

View File

@ -1,11 +1,3 @@
# TODO: Support `SOCK_SEQPACKET` sockets
AllUnixDomainSockets/UnixSocketPairTest.BindToBadName/*
AllUnixDomainSockets/UnixSocketPairTest.BindToBadFamily/*
AllUnixDomainSockets/*/4
AllUnixDomainSockets/*/5
AllUnixDomainSockets/*/10
AllUnixDomainSockets/*/11
# TODO: Support `SOCK_DGRAM` sockets
AllUnixDomainSockets/*/2
AllUnixDomainSockets/*/3
@ -23,5 +15,17 @@ AllUnixDomainSockets/UnixSocketPairTest.NetdeviceIoctlsSucceed/*
# TODO: Fix operations on `/proc/*/fd/*`
AllUnixDomainSockets/UnixSocketPairTest.SocketReopenFromProcfs/*
# TODO: Support control messages
AllUnixDomainSockets/UnixSocketPairCmsgTest.*
# TODO: Report `MSG_(C)TRUNC` after truncating the (control) message
AllUnixDomainSockets/UnixSocketPairCmsgTest.BasicFDPassNoSpaceMsgCtrunc/*
AllUnixDomainSockets/UnixSocketPairCmsgTest.BasicFDPassNullControlMsgCtrunc/*
AllUnixDomainSockets/UnixSocketPairCmsgTest.BasicFDPassNotEnoughSpaceMsgCtrunc/*
AllUnixDomainSockets/UnixSocketPairCmsgTest.BasicThreeFDPassTruncationMsgCtrunc/*
AllUnixDomainSockets/UnixSocketPairCmsgTest.BasicTwoFDPassUnalignedRecvTruncationMsgTrunc/*
AllUnixDomainSockets/UnixSocketPairCmsgTest.CredPassNoSpaceMsgCtrunc/*
AllUnixDomainSockets/UnixSocketPairCmsgTest.CredPassTruncatedMsgCtrunc/*
# TODO: Support `MSG_CMSG_CLOEXEC`
AllUnixDomainSockets/UnixSocketPairCmsgTest.CloexecRecvFDPass/*
# TODO: Support `MSG_PEEK`
AllUnixDomainSockets/UnixSocketPairCmsgTest.FDPassPeek/*

View File

@ -0,0 +1,13 @@
# TODO: Support `MSG_DONTWAIT` and `MSG_PEEK`
SeqpacketUnixSockets/NonStreamSocketPairTest.SplitRecv/*
SeqpacketUnixSockets/NonStreamSocketPairTest.SinglePeek/*
SeqpacketUnixSockets/NonStreamSocketPairTest.RecvmsgTruncPeekDontwaitZeroLen/*
# TODO: Support `SO_SNDTIMEO`
SeqpacketUnixSockets/UnixNonStreamSocketPairTest.SendTimeout/*
# TODO: Support `MSG_TRUNC`
SeqpacketUnixSockets/NonStreamSocketPairTest.MsgTruncTruncation/*
SeqpacketUnixSockets/NonStreamSocketPairTest.MsgTruncTruncationRecvmsgMsghdrFlagMsgTrunc/*
SeqpacketUnixSockets/NonStreamSocketPairTest.RecvmsgMsgTruncZeroLen/*
SeqpacketUnixSockets/NonStreamSocketPairTest.RecvmsgMsgTruncMsgPeekZeroLen/*