Add AF_UNIX sockets implementation (#11681)

* components/dfs: support unix socket nodes

AF_UNIX pathname binding and descriptor passing require DFSv2 socket
nodes and retained open file descriptions across fd tables.

Add socket-node creation for tmpfs and devtmpfs, fd reference helpers,
and socket F_SETFL forwarding.

Impact: DFSv2 socket nodes and descriptor reference handling.
Validation: git diff --cached --check.

* components/net/sal: support local protocol providers

Local IPC protocol families do not have a backing network device.
Store the selected provider in each SAL socket and add a local provider
registry while preserving netdev checks for Internet sockets.

Handle DFSv2 close semantics, socketpair flags, and MSG_CTRUNC for
AF_UNIX integration.

Impact: SAL protocol dispatch for all socket families.
Validation: git diff --cached --check.

* components/lwp: support unix socket messages

Musl AF_UNIX addresses and ancillary data require explicit ABI and
user-memory conversion at the LWP syscall boundary.

Add the musl msghdr layout, bounded address and message copying,
control-message level conversion, and MSG_CTRUNC translation. Correct
receive buffer allocation and copy lengths while handling messages.

Impact: LWP socket syscalls when SAL is enabled.
Validation: git diff --cached --check.

* components/net/af_unix: add local sockets

Add an opt-in AF_UNIX provider for pathname-based local IPC without
a synthetic network device.

Support datagram and stream sockets, blocking and nonblocking I/O,
timeouts, poll, socketpair, and SCM_RIGHTS descriptor passing. Include
bounded Kconfig settings, component documentation, and utest coverage.

Impact: enabled only by RT_USING_AF_UNIX and requires SAL POSIX with
DFSv2.
Validation: git diff --cached --check.

* Fix AF_UNIX CI checks

Apply the repository clang-format rules to the affected source lines.
Suppress the cppcheck false positive for the devtmpfs list iterator.

No functional behavior is changed.

* components/lwp: preserve NULL optional msghdr buffers

When msg_name or msg_control is NULL, keep the kernel pointer NULL
instead of substituting an uninitialized buffer. Reject a NULL control
buffer with a nonzero length as EFAULT.

Impact: LWP sendmsg/recvmsg conversion on MMU targets.
Validation: git diff --cached --check.

* components/net/af_unix: fix poll UAF, namespace leak, and SCM_RIGHTS cycles

Poll only the local wait queue and wake writers from the receiver.
Free namespace entries on detach, and reject AF_UNIX descriptors in
SCM_RIGHTS until cycle collection exists.

Impact: AF_UNIX poll, bind lifetime, and descriptor passing.
Validation: git diff --cached --check.

* components/dfs: add DFSv2 AF_UNIX utest suite

Add opt-in tests under dfs/utest/v2/af_unix for socket nodes, IPC,
poll peer-close, namespace cleanup, and SCM_RIGHTS rejection.

Sources are built only when RT_UTEST_TC_USING_DFS_V2_AF_UNIX and the
selected test groups are enabled.

Impact: DFS utest menu and build; no production AF_UNIX behavior change.
Validation: git diff --cached --check.
This commit is contained in:
Bernard Xiong
2026-08-17 17:15:34 +08:00
committed by GitHub
parent d2acd7587e
commit 991f0ffca6
36 changed files with 5727 additions and 242 deletions
@@ -27,6 +27,7 @@
#define TMPFS_TYPE_FILE 0x00
#define TMPFS_TYPE_DIR 0x01
#define TMPFS_TYPE_DYN_DEV 0x02 /* dynamic device */
#define TMPFS_TYPE_SOCKET 0x03
struct devtmpfs_sb;
@@ -223,6 +224,7 @@ find_subpath:
if (rt_strcmp(file->name, filename) == 0)
{
rt_spin_unlock(&superblock->lock);
/* cppcheck-suppress uninitvar */
return file;
}
}
@@ -325,6 +327,10 @@ static int devtmpfs_getdents(struct dfs_file *file, struct dirent *dirp, uint32_
{
d->d_type = DT_DIR;
}
if (n_file->type == TMPFS_TYPE_SOCKET)
{
d->d_type = DT_SOCK;
}
d->d_reclen = (rt_uint16_t)sizeof(struct dirent);
rt_strncpy(d->d_name, n_file->name, DIRENT_NAME_MAX);
@@ -540,6 +546,14 @@ static struct dfs_vnode *devtmpfs_create_vnode(struct dfs_dentry *dentry, int ty
vnode->mode &= ~S_IFMT;
vnode->mode |= S_IFDIR;
}
else if (type == FT_SOCKET ||
(type == FT_REGULAR && S_ISSOCK(mode)))
{
d_file->type = TMPFS_TYPE_SOCKET;
vnode->type = FT_SOCKET;
vnode->mode &= ~S_IFMT;
vnode->mode |= S_IFSOCK;
}
else
{
d_file->type = TMPFS_TYPE_FILE;
@@ -585,6 +599,10 @@ static struct dfs_vnode *devtmpfs_lookup(struct dfs_dentry *dentry)
{
vnode->type = FT_DIRECTORY;
}
else if (d_file->type == TMPFS_TYPE_SOCKET)
{
vnode->type = FT_SOCKET;
}
else if (d_file->link)
{
vnode->type = FT_SYMLINK;
@@ -535,6 +535,10 @@ static int dfs_tmpfs_getdents(struct dfs_file *file,
{
d->d_type = DT_DIR;
}
if (n_file->type == TMPFS_TYPE_SOCKET)
{
d->d_type = DT_SOCK;
}
d->d_namlen = RT_NAME_MAX;
d->d_reclen = (rt_uint16_t)sizeof(struct dirent);
rt_strncpy(d->d_name, n_file->name, TMPFS_NAME_MAX);
@@ -664,6 +668,11 @@ static struct dfs_vnode *_dfs_tmpfs_lookup(struct dfs_dentry *dentry)
vnode->mode = S_IFDIR | (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
vnode->type = FT_DIRECTORY;
}
else if (d_file->type == TMPFS_TYPE_SOCKET)
{
vnode->mode = S_IFSOCK | (S_IRWXU | S_IRWXG | S_IRWXO);
vnode->type = FT_SOCKET;
}
else
{
vnode->mode = S_IFREG | (S_IRWXU | S_IRWXG | S_IRWXO);
@@ -750,6 +759,13 @@ static struct dfs_vnode *dfs_tmpfs_create_vnode(struct dfs_dentry *dentry, int t
vnode->mode = S_IFDIR | (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
vnode->type = FT_DIRECTORY;
}
else if (type == FT_SOCKET ||
(type == FT_REGULAR && S_ISSOCK(mode)))
{
d_file->type = TMPFS_TYPE_SOCKET;
vnode->mode = S_IFSOCK | (mode & (S_IRWXU | S_IRWXG | S_IRWXO));
vnode->type = FT_SOCKET;
}
else
{
d_file->type = TMPFS_TYPE_FILE;
@@ -19,6 +19,7 @@
#define TMPFS_TYPE_FILE 0x00
#define TMPFS_TYPE_DIR 0x01
#define TMPFS_TYPE_SOCKET 0x02
struct tmpfs_sb;
@@ -46,4 +47,3 @@ struct tmpfs_sb
int dfs_tmpfs_init(void);
#endif
+6
View File
@@ -134,6 +134,12 @@ int fdt_fd_associate_file(struct dfs_fdtable *fdt, int fd, struct dfs_file *file
struct dfs_file *fd_get(int fd);
void fd_release(int fd);
/* Reference helpers used when an open file description crosses fd tables. */
int dfs_file_get_refs(const int *fds, size_t count, struct dfs_file **files);
/* Successful installation transfers the supplied references to the fd table. */
int dfs_file_install_refs(struct dfs_file **files, size_t count, int *fds);
void dfs_file_put_ref(struct dfs_file *file);
void fd_init(struct dfs_file *fd);
struct dfs_fdtable *dfs_fdtable_get(void);
+1
View File
@@ -149,6 +149,7 @@ void dfs_file_init(struct dfs_file *file);
void dfs_file_deinit(struct dfs_file *file);
int dfs_file_open(struct dfs_file *file, const char *path, int flags, mode_t mode);
int dfs_file_mknod(const char *path, int type, mode_t mode);
int dfs_file_close(struct dfs_file *file);
off_t dfs_file_get_fpos(struct dfs_file *file);
+110 -4
View File
@@ -349,23 +349,29 @@ int fdt_fd_new(struct dfs_fdtable *fdt)
*/
void fdt_fd_release(struct dfs_fdtable *fdt, int fd)
{
if (fd < fdt->maxfd)
if (fdt == RT_NULL || dfs_file_lock() != RT_EOK)
{
return;
}
if (fd >= 0 && fd < (int)fdt->maxfd)
{
struct dfs_file *file;
file = fdt_get_file(fdt, fd);
if (file && file->ref_count == 1)
if (file != RT_NULL && file->ref_count == 1)
{
dfs_file_destroy(file);
}
else
else if (file != RT_NULL)
{
rt_atomic_sub(&(file->ref_count), 1);
}
fdt->fds[fd] = RT_NULL;
}
dfs_file_unlock();
}
/**
@@ -493,6 +499,106 @@ struct dfs_file *fd_get(int fd)
return fdt_get_file(fdt, fd);
}
int dfs_file_get_refs(const int *fds, size_t count, struct dfs_file **files)
{
size_t index;
struct dfs_fdtable *fdt;
if ((count != 0 && (fds == RT_NULL || files == RT_NULL)) ||
dfs_file_lock() != RT_EOK)
{
return -EINVAL;
}
fdt = dfs_fdtable_get();
for (index = 0; index < count; index++)
{
files[index] = fdt_get_file(fdt, fds[index]);
if (files[index] == RT_NULL ||
(files[index]->dentry == RT_NULL && files[index]->vnode == RT_NULL))
{
dfs_file_unlock();
return -EBADF;
}
}
for (index = 0; index < count; index++)
{
rt_atomic_add(&files[index]->ref_count, 1);
}
dfs_file_unlock();
return 0;
}
int dfs_file_install_refs(struct dfs_file **files, size_t count, int *fds)
{
int fd;
int startfd;
size_t index;
struct dfs_fdtable *fdt;
if ((count != 0 && (files == RT_NULL || fds == RT_NULL)) ||
dfs_file_lock() != RT_EOK)
{
return -EINVAL;
}
fdt = dfs_fdtable_get();
startfd = (fdt == &_fdtab) ? DFS_STDIO_OFFSET : 0;
for (index = 0; index < count; index++)
{
if (files[index] == RT_NULL || files[index]->magic != DFS_FD_MAGIC)
{
break;
}
fd = _fdt_slot_alloc(fdt, startfd);
if (fd < 0)
{
break;
}
fdt->fds[fd] = files[index];
fds[index] = fd;
}
if (index != count)
{
while (index > 0)
{
index--;
fdt->fds[fds[index]] = RT_NULL;
}
dfs_file_unlock();
return -EMFILE;
}
dfs_file_unlock();
return 0;
}
void dfs_file_put_ref(struct dfs_file *file)
{
if (file == RT_NULL || dfs_file_lock() != RT_EOK)
{
return;
}
if (file->magic == DFS_FD_MAGIC &&
rt_atomic_load(&file->ref_count) > 0 &&
dfs_file_close(file) == 0)
{
if (rt_atomic_load(&file->ref_count) == 1)
{
dfs_file_destroy(file);
}
else
{
rt_atomic_sub(&file->ref_count, 1);
}
}
dfs_file_unlock();
}
/**
* This function will get the file descriptor table of current process.
*/
@@ -1238,4 +1344,4 @@ MSH_CMD_EXPORT(dfs_dlog, dfs dlog on|off);
#endif
#endif
/** @} */
/** @} */
+132 -1
View File
@@ -823,6 +823,128 @@ _ERR_RET:
return ret;
}
int dfs_file_mknod(const char *path, int type, mode_t mode)
{
int ret = -EINVAL;
int create_type = type;
mode_t create_mode = mode;
char *fullpath;
struct dfs_mnt *mnt;
struct dfs_dentry *dentry;
if (path == RT_NULL || type < FT_REGULAR || type > FT_NONLOCK)
{
return -EINVAL;
}
fullpath = dfs_normalize_path(RT_NULL, path);
if (fullpath == RT_NULL)
{
return -ENOMEM;
}
mnt = dfs_mnt_lookup(fullpath);
if (mnt == RT_NULL)
{
ret = -ENOENT;
goto __exit;
}
{
char *realpath;
realpath = dfs_file_realpath(&mnt, fullpath, DFS_REALPATH_EXCEPT_LAST);
if (realpath != RT_NULL)
{
rt_free(fullpath);
fullpath = realpath;
}
}
if (strcmp(mnt->fullpath, fullpath) == 0)
{
ret = -EEXIST;
goto __exit;
}
dentry = dfs_dentry_lookup(mnt, fullpath, 0);
if (dentry != RT_NULL)
{
dfs_dentry_unref(dentry);
ret = -EEXIST;
goto __exit;
}
if (mnt->fs_ops->create_vnode == RT_NULL)
{
ret = -ENOSYS;
goto __exit;
}
if (type == FT_SOCKET)
{
create_type = FT_REGULAR;
create_mode = (mode & ~S_IFMT) | S_IFSOCK;
}
ret = dfs_file_lock();
if (ret != RT_EOK)
{
goto __exit;
}
dentry = dfs_dentry_create(mnt, fullpath);
if (dentry != RT_NULL)
{
struct dfs_vnode *vnode = RT_NULL;
if (dfs_is_mounted(mnt) == 0)
{
vnode = mnt->fs_ops->create_vnode(dentry, create_type,
create_mode);
}
if (vnode != RT_NULL)
{
if (type == FT_SOCKET && !S_ISSOCK(vnode->mode))
{
if (mnt->fs_ops->unlink != RT_NULL)
{
dentry->vnode = vnode;
(void)mnt->fs_ops->unlink(dentry);
dentry->vnode = RT_NULL;
}
dfs_vnode_unref(vnode);
ret = -EOPNOTSUPP;
}
else
{
vnode->type = type;
dentry->vnode = vnode;
dfs_dentry_insert(dentry);
ret = RT_EOK;
}
}
else
{
ret = -ENOENT;
}
}
else
{
ret = -ENOMEM;
}
dfs_file_unlock();
if (dentry != RT_NULL)
{
dfs_dentry_unref(dentry);
}
__exit:
rt_free(fullpath);
return ret;
}
/**
* @brief Close a file and release associated resources
*
@@ -1588,6 +1710,15 @@ int dfs_file_fcntl(int fd, int cmd, unsigned long arg)
O_APPEND | O_NONBLOCK;
flags &= mask;
if (file->vnode->type == FT_SOCKET && file->fops != RT_NULL &&
file->fops->ioctl != RT_NULL)
{
ret = file->fops->ioctl(file, F_SETFL, (void *)(rt_base_t)flags);
if (ret < 0)
{
break;
}
}
file->flags &= ~mask;
file->flags |= flags;
break;
@@ -3035,4 +3166,4 @@ void copy(const char *src, const char *dst)
}
FINSH_FUNCTION_EXPORT(copy, copy file or dir)
#endif
#endif
+3
View File
@@ -40,5 +40,8 @@ if RT_USING_DFS
This device will be formatted and mounted during testing
endif
rsource "v2/af_unix/Kconfig"
endmenu
endif
+4 -2
View File
@@ -1,5 +1,6 @@
Import('rtconfig')
from building import *
import os
cwd = GetCurrentDir()
src = []
@@ -15,6 +16,7 @@ if GetDepend('RT_UTEST_TC_USING_DFS_API'):
src += ['tc_posix_api.c']
# Define the test group with proper dependencies
group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_DFS'], CPPPATH = CPPPATH)
objs = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_DFS'], CPPPATH = CPPPATH)
objs = objs + SConscript(os.path.join('v2', 'af_unix', 'SConscript'))
Return('group')
Return('objs')
+95
View File
@@ -0,0 +1,95 @@
menuconfig RT_UTEST_TC_USING_DFS_V2_AF_UNIX
bool "DFSv2 AF_UNIX Test"
default n
depends on RT_USING_DFS_V2
depends on RT_USING_AF_UNIX
depends on SAL_USING_POSIX
help
Enable DFSv2 AF_UNIX unit tests. Sources are compiled only when
this option and the selected test groups below are enabled.
Coverage includes socket-node creation, transferable file
references, datagram and stream IPC, poll readiness, pathname
lifetime, namespace cleanup, and SCM_RIGHTS handling.
if RT_UTEST_TC_USING_DFS_V2_AF_UNIX
config RT_UTEST_DFS_V2_AF_UNIX_DIR
string "Directory for AF_UNIX pathname tests"
default "/dev"
help
Directory used to create temporary AF_UNIX pathname nodes.
The filesystem mounted here must support S_IFSOCK nodes
through create_vnode(), such as tmpfs or devtmpfs.
config RT_UTEST_DFS_V2_AF_UNIX_NODE
bool "Socket node and file-reference tests"
default y
help
Build tests for DFSv2 socket nodes and transferable file
references:
* dfs_file_mknod(FT_SOCKET) creates an S_ISSOCK node
* creating the same path again returns EEXIST
* unlink() removes the socket node
* dfs_file_get_refs() rejects an invalid fd
* get_refs() + put_ref() keeps the original fd usable
* get_refs() + install_refs() transfers a pipe and shares
the open file description
config RT_UTEST_DFS_V2_AF_UNIX_IPC
bool "Datagram, stream and socketpair IPC tests"
default y
help
Build tests for AF_UNIX IPC operations:
* SOCK_DGRAM bind, connect, send and recvfrom
* SOCK_STREAM listen, accept, send, recv and shutdown
* socketpair() for both SOCK_DGRAM and SOCK_STREAM
* sendmsg()/recvmsg() scatter-gather I/O
* connected sendmsg() with a NULL msg_name
* sendmsg() with a NULL msg_control and nonzero length
* listen() on SOCK_DGRAM returns EOPNOTSUPP
* connect() to a missing pathname returns ENOENT
* non-blocking recv() returns EAGAIN
config RT_UTEST_DFS_V2_AF_UNIX_POLL
bool "Poll readiness and peer-close tests"
default y
help
Build tests for AF_UNIX poll/select readiness:
* empty stream socket reports POLLOUT and not POLLIN
* received data reports POLLIN
* filling the stream buffer clears POLLOUT; a peer recv
makes POLLOUT ready again
* blocking poll(POLLIN) wakes with POLLHUP/POLLIN when
the peer is closed (covers the peer-queue UAF case)
* datagram queue-full and POLLIN readiness
config RT_UTEST_DFS_V2_AF_UNIX_NAMESPACE
bool "Pathname bind, unlink and namespace tests"
default y
help
Build tests for pathname lifetime and namespace cleanup:
* bind() creates a persistent S_IFSOCK node
* close() leaves the pathname in the filesystem
* rebinding without unlink() returns EADDRINUSE
* unlink() then bind() succeeds
* connect() after close without unlink() returns
ECONNREFUSED
* connect() after unlink() returns ENOENT
* repeated bind/close/unlink on distinct paths does not
leak namespace entries
config RT_UTEST_DFS_V2_AF_UNIX_RIGHTS
bool "SCM_RIGHTS transfer and rejection tests"
default y
help
Build tests for SCM_RIGHTS descriptor passing:
* transfer pipe descriptors over SOCK_DGRAM and
SOCK_STREAM
* recvmsg() without a control buffer reports MSG_CTRUNC
* MSG_PEEK does not install or consume descriptors
* an invalid fd returns EBADF
* passing an AF_UNIX socket descriptor returns
EOPNOTSUPP and does not create a reference cycle
endif
@@ -0,0 +1,27 @@
Import('rtconfig')
from building import *
cwd = GetCurrentDir()
src = []
CPPPATH = [cwd]
if GetDepend('RT_UTEST_TC_USING_DFS_V2_AF_UNIX'):
cases = []
if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_NODE'):
cases += ['tc_af_unix_node.c']
if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_IPC'):
cases += ['tc_af_unix_ipc.c']
if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_POLL'):
cases += ['tc_af_unix_poll.c']
if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_NAMESPACE'):
cases += ['tc_af_unix_namespace.c']
if GetDepend('RT_UTEST_DFS_V2_AF_UNIX_RIGHTS'):
cases += ['tc_af_unix_rights.c']
if cases:
src += ['tc_af_unix_common.c'] + cases
group = DefineGroup('utestcases', src,
depend = ['RT_USING_UTESTCASES', 'RT_USING_DFS_V2', 'RT_USING_AF_UNIX'],
CPPPATH = CPPPATH)
Return('group')
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2006-2026, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef TC_AF_UNIX_H__
#define TC_AF_UNIX_H__
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dfs.h>
#include <dfs_file.h>
#include <rtthread.h>
#include <utest.h>
#ifndef RT_UTEST_DFS_V2_AF_UNIX_DIR
#define RT_UTEST_DFS_V2_AF_UNIX_DIR "/dev"
#endif
#define TC_AF_UNIX_PATH_MAX 64
void tc_af_unix_path(char *buf, rt_size_t size, const char *name);
void tc_af_unix_make_address(struct sockaddr_un *address, const char *path);
void tc_af_unix_closesocket(int *fd);
void tc_af_unix_close(int *fd);
int tc_af_unix_send_rights(int socket_fd, const int *fds, size_t fd_count,
char payload);
int tc_af_unix_receive_rights(int socket_fd, int flags, size_t control_length,
int *fds, size_t *fd_count, int *message_flags,
char *payload);
#endif /* TC_AF_UNIX_H__ */
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2006-2026, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "tc_af_unix.h"
void tc_af_unix_path(char *buf, rt_size_t size, const char *name)
{
rt_snprintf(buf, size, "%s/%s", RT_UTEST_DFS_V2_AF_UNIX_DIR, name);
}
void tc_af_unix_make_address(struct sockaddr_un *address, const char *path)
{
rt_memset(address, 0, sizeof(*address));
address->sa_family = AF_UNIX;
rt_strncpy(address->sun_path, path, sizeof(address->sun_path) - 1);
}
void tc_af_unix_closesocket(int *fd)
{
if (*fd >= 0)
{
closesocket(*fd);
*fd = -1;
}
}
void tc_af_unix_close(int *fd)
{
if (*fd >= 0)
{
close(*fd);
*fd = -1;
}
}
int tc_af_unix_send_rights(int socket_fd, const int *fds, size_t fd_count,
char payload)
{
char control[CMSG_SPACE(2 * sizeof(int))];
struct cmsghdr *cmsg;
struct iovec iov;
struct msghdr message;
rt_memset(&message, 0, sizeof(message));
rt_memset(control, 0, sizeof(control));
iov.iov_base = &payload;
iov.iov_len = sizeof(payload);
message.msg_iov = &iov;
message.msg_iovlen = 1;
message.msg_control = control;
message.msg_controllen = CMSG_SPACE(fd_count * sizeof(int));
cmsg = CMSG_FIRSTHDR(&message);
cmsg->cmsg_len = CMSG_LEN(fd_count * sizeof(int));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
rt_memcpy(CMSG_DATA(cmsg), fds, fd_count * sizeof(int));
return sendmsg(socket_fd, &message, 0);
}
int tc_af_unix_receive_rights(int socket_fd, int flags, size_t control_length,
int *fds, size_t *fd_count, int *message_flags,
char *payload)
{
char control[CMSG_SPACE(2 * sizeof(int))];
int result;
size_t count = 0;
struct cmsghdr *cmsg;
struct iovec iov;
struct msghdr message;
rt_memset(&message, 0, sizeof(message));
rt_memset(control, 0, sizeof(control));
iov.iov_base = payload;
iov.iov_len = sizeof(*payload);
message.msg_iov = &iov;
message.msg_iovlen = 1;
message.msg_control = control_length != 0 ? control : RT_NULL;
message.msg_controllen = control_length;
result = recvmsg(socket_fd, &message, flags);
if (result >= 0)
{
cmsg = CMSG_FIRSTHDR(&message);
if (cmsg != RT_NULL && cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_RIGHTS &&
cmsg->cmsg_len >= CMSG_LEN(sizeof(int)))
{
count = (cmsg->cmsg_len - sizeof(*cmsg)) / sizeof(int);
rt_memcpy(fds, CMSG_DATA(cmsg), count * sizeof(int));
}
*fd_count = count;
*message_flags = message.msg_flags;
}
return result;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,175 @@
/*
* Copyright (c) 2006-2026, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "tc_af_unix.h"
#define TC_AF_UNIX_NAMESPACE_REPEAT 8
static void tc_af_unix_pathname_lifetime(void)
{
char path[TC_AF_UNIX_PATH_MAX];
int error;
int first = -1;
int result;
int second = -1;
struct sockaddr_un address;
struct stat file_stat;
tc_af_unix_path(path, sizeof(path), "utafupath");
(void)unlink(path);
tc_af_unix_make_address(&address, path);
first = socket(AF_UNIX, SOCK_DGRAM, 0);
uassert_true(first >= 0);
if (first < 0)
{
goto __exit;
}
uassert_int_equal(bind(first, (struct sockaddr *)&address, sizeof(address)),
0);
uassert_int_equal(stat(path, &file_stat), 0);
uassert_true(S_ISSOCK(file_stat.st_mode));
tc_af_unix_closesocket(&first);
uassert_int_equal(stat(path, &file_stat), 0);
uassert_true(S_ISSOCK(file_stat.st_mode));
second = socket(AF_UNIX, SOCK_DGRAM, 0);
uassert_true(second >= 0);
if (second < 0)
{
goto __exit;
}
result = bind(second, (struct sockaddr *)&address, sizeof(address));
error = rt_get_errno();
uassert_int_equal(result, -1);
uassert_int_equal(error, EADDRINUSE);
uassert_int_equal(unlink(path), 0);
uassert_int_equal(bind(second, (struct sockaddr *)&address,
sizeof(address)),
0);
__exit:
tc_af_unix_closesocket(&second);
tc_af_unix_closesocket(&first);
(void)unlink(path);
}
static void tc_af_unix_lookup_after_close(void)
{
char path[TC_AF_UNIX_PATH_MAX];
int client = -1;
int error;
int result;
int server = -1;
struct sockaddr_un address;
tc_af_unix_path(path, sizeof(path), "utafuref");
(void)unlink(path);
tc_af_unix_make_address(&address, path);
server = socket(AF_UNIX, SOCK_STREAM, 0);
client = socket(AF_UNIX, SOCK_STREAM, 0);
uassert_true(server >= 0);
uassert_true(client >= 0);
if (server < 0 || client < 0)
{
goto __exit;
}
uassert_int_equal(bind(server, (struct sockaddr *)&address,
sizeof(address)),
0);
uassert_int_equal(listen(server, 1), 0);
tc_af_unix_closesocket(&server);
result = connect(client, (struct sockaddr *)&address, sizeof(address));
error = rt_get_errno();
uassert_int_equal(result, -1);
uassert_int_equal(error, ECONNREFUSED);
uassert_int_equal(unlink(path), 0);
result = connect(client, (struct sockaddr *)&address, sizeof(address));
error = rt_get_errno();
uassert_int_equal(result, -1);
uassert_int_equal(error, ENOENT);
__exit:
tc_af_unix_closesocket(&client);
tc_af_unix_closesocket(&server);
(void)unlink(path);
}
static void tc_af_unix_namespace_reuse(void)
{
char name[16];
char path[TC_AF_UNIX_PATH_MAX];
int client = -1;
int error;
int index;
int result;
int sock = -1;
struct sockaddr_un address;
for (index = 0; index < TC_AF_UNIX_NAMESPACE_REPEAT; index++)
{
rt_snprintf(name, sizeof(name), "utafuns%d", index);
tc_af_unix_path(path, sizeof(path), name);
(void)unlink(path);
tc_af_unix_make_address(&address, path);
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
uassert_true(sock >= 0);
if (sock < 0)
{
return;
}
uassert_int_equal(bind(sock, (struct sockaddr *)&address,
sizeof(address)),
0);
tc_af_unix_closesocket(&sock);
uassert_int_equal(unlink(path), 0);
}
tc_af_unix_path(path, sizeof(path), "utafuns0");
tc_af_unix_make_address(&address, path);
client = socket(AF_UNIX, SOCK_DGRAM, 0);
uassert_true(client >= 0);
if (client < 0)
{
return;
}
result = connect(client, (struct sockaddr *)&address, sizeof(address));
error = rt_get_errno();
uassert_int_equal(result, -1);
uassert_int_equal(error, ENOENT);
tc_af_unix_closesocket(&client);
tc_af_unix_path(path, sizeof(path), "utafunew");
(void)unlink(path);
tc_af_unix_make_address(&address, path);
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
uassert_true(sock >= 0);
if (sock < 0)
{
return;
}
uassert_int_equal(bind(sock, (struct sockaddr *)&address, sizeof(address)),
0);
tc_af_unix_closesocket(&sock);
(void)unlink(path);
}
static void tc_af_unix_namespace_testcase(void)
{
UTEST_UNIT_RUN(tc_af_unix_pathname_lifetime);
UTEST_UNIT_RUN(tc_af_unix_lookup_after_close);
UTEST_UNIT_RUN(tc_af_unix_namespace_reuse);
}
UTEST_TC_EXPORT(tc_af_unix_namespace_testcase,
"components.dfs.v2.af_unix.namespace", RT_NULL, RT_NULL, 15);
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2006-2026, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "tc_af_unix.h"
static void tc_af_unix_mknod_socket(void)
{
char path[TC_AF_UNIX_PATH_MAX];
int result;
struct stat file_stat;
tc_af_unix_path(path, sizeof(path), "utafumkn");
(void)unlink(path);
result = dfs_file_mknod(path, FT_SOCKET, S_IRWXU | S_IRWXG | S_IRWXO);
uassert_int_equal(result, 0);
if (result != 0)
{
return;
}
uassert_int_equal(stat(path, &file_stat), 0);
uassert_true(S_ISSOCK(file_stat.st_mode));
result = dfs_file_mknod(path, FT_SOCKET, S_IRWXU | S_IRWXG | S_IRWXO);
uassert_int_equal(result, -EEXIST);
uassert_int_equal(unlink(path), 0);
uassert_true(stat(path, &file_stat) < 0);
}
static void tc_af_unix_file_refs(void)
{
char data[4];
int installed = -1;
int invalid_fd = -1;
int pipe_fds[2] = { -1, -1 };
struct dfs_file *files[1];
files[0] = RT_NULL;
uassert_int_equal(dfs_file_get_refs(&invalid_fd, 1, files), -EBADF);
uassert_true(files[0] == RT_NULL);
uassert_int_equal(pipe(pipe_fds), 0);
if (pipe_fds[0] < 0)
{
return;
}
uassert_int_equal(write(pipe_fds[1], "abcd", 4), 4);
uassert_int_equal(dfs_file_get_refs(&pipe_fds[0], 1, files), 0);
uassert_true(files[0] != RT_NULL);
dfs_file_put_ref(files[0]);
uassert_int_equal(read(pipe_fds[0], data, 2), 2);
uassert_buf_equal(data, "ab", 2);
uassert_int_equal(dfs_file_get_refs(&pipe_fds[0], 1, files), 0);
uassert_int_equal(dfs_file_install_refs(files, 1, &installed), 0);
uassert_true(installed >= 0);
uassert_int_equal(read(installed, data, 2), 2);
uassert_buf_equal(data, "cd", 2);
tc_af_unix_close(&installed);
tc_af_unix_close(&pipe_fds[0]);
tc_af_unix_close(&pipe_fds[1]);
}
static void tc_af_unix_node_testcase(void)
{
UTEST_UNIT_RUN(tc_af_unix_mknod_socket);
UTEST_UNIT_RUN(tc_af_unix_file_refs);
}
UTEST_TC_EXPORT(tc_af_unix_node_testcase, "components.dfs.v2.af_unix.node",
RT_NULL, RT_NULL, 10);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+27
View File
@@ -108,6 +108,33 @@ struct musl_sockaddr
char sa_data[14];
};
struct musl_msghdr
{
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
#if defined(ARCH_CPU_64BIT) && defined(__BYTE_ORDER__) && \
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
int padding1;
#endif
int msg_iovlen;
#if defined(ARCH_CPU_64BIT) && defined(__BYTE_ORDER__) && \
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
int padding1;
#endif
void *msg_control;
#if defined(ARCH_CPU_64BIT) && defined(__BYTE_ORDER__) && \
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
int padding2;
#endif
socklen_t msg_controllen;
#if defined(ARCH_CPU_64BIT) && defined(__BYTE_ORDER__) && \
__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
int padding2;
#endif
int msg_flags;
};
struct musl_ifmap {
unsigned long int mem_start;
unsigned long int mem_end;
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1,6 +1,7 @@
menu "Network"
rsource "sal/Kconfig"
rsource "af_unix/Kconfig"
rsource "netdev/Kconfig"
rsource "lwip/Kconfig"
rsource "at/Kconfig"
+42
View File
@@ -0,0 +1,42 @@
menuconfig RT_USING_AF_UNIX
bool "AF_UNIX local sockets"
depends on RT_USING_SAL
depends on SAL_USING_POSIX
depends on RT_USING_DFS_V2
default n
help
Enable pathname-based AF_UNIX SOCK_DGRAM and SOCK_STREAM sockets.
if RT_USING_AF_UNIX
config AF_UNIX_DGRAM_MAX_SIZE
int "Maximum datagram size"
range 128 65535
default 4096
config AF_UNIX_DGRAM_QUEUE_LEN
int "Datagram receive queue length"
range 1 256
default 16
config AF_UNIX_STREAM_BUFFER_SIZE
int "Stream receive buffer size"
range 256 65535
default 4096
config AF_UNIX_LISTEN_BACKLOG_MAX
int "Maximum stream listen backlog"
range 1 128
default 16
config AF_UNIX_RIGHTS_MAX
int "Maximum file descriptors per SCM_RIGHTS message"
range 1 253
default 16
config RT_AF_UNIX_USING_TESTCASES
bool "Build AF_UNIX test cases"
depends on RT_USING_UTESTCASES
default n
endif
+57
View File
@@ -0,0 +1,57 @@
# AF_UNIX local sockets
The AF_UNIX component provides pathname-based local IPC through the existing
SAL and POSIX socket APIs. It supports `SOCK_DGRAM`, `SOCK_STREAM`, and
`socketpair()` for both socket types.
## Configuration
Enable `RT_USING_AF_UNIX`. The component requires `RT_USING_SAL`,
`SAL_USING_POSIX`, and `RT_USING_DFS_V2`.
- `AF_UNIX_DGRAM_MAX_SIZE` bounds one datagram.
- `AF_UNIX_DGRAM_QUEUE_LEN` bounds queued datagrams per socket.
- `AF_UNIX_STREAM_BUFFER_SIZE` bounds each stream receive buffer.
- `AF_UNIX_LISTEN_BACKLOG_MAX` caps the stream accept queue.
- `AF_UNIX_RIGHTS_MAX` bounds the file descriptors in one `SCM_RIGHTS` send.
- `RT_AF_UNIX_USING_TESTCASES` builds the component utest suite.
## Pathname behavior
`bind()` creates an `S_IFSOCK` node through DFSv2. The mounted filesystem must
support special nodes through `create_vnode()`; tmpfs and devtmpfs support
socket nodes directly. Closing a bound socket leaves its pathname in the
filesystem. Applications should call `unlink()` before rebinding, which
matches common Unix daemon behavior.
Removing a pathname prevents new lookups. Existing stream connections and
connected datagram endpoints continue to reference their established peers.
## Descriptor passing
`sendmsg()` and `recvmsg()` support one or more file descriptors in
`SOL_SOCKET`/`SCM_RIGHTS` control messages. The queued reference remains valid
after the sender closes its descriptor. On receive, each reference is installed
as a new descriptor in the receiving process and retains the same open file
description, including its shared file position. Passing an AF_UNIX socket
descriptor is not supported and returns `EOPNOTSUPP`.
For datagram sockets, the control message is atomic with its datagram. For
stream sockets, it is associated with the first byte written by `sendmsg()` and
is delivered when a receive consumes that byte. A receive without a control
buffer discards associated descriptors and reports `MSG_CTRUNC`; `MSG_PEEK`
does not install or consume descriptors. At least one payload byte is required
when sending `SCM_RIGHTS`.
Only `SCM_RIGHTS` ancillary data is supported. Credentials and other control
message types return `EOPNOTSUPP`.
## Supported operations
The component implements bind, connect, listen, accept, send/receive,
sendto/recvfrom, sendmsg/recvmsg with descriptor passing, shutdown, socket
options, nonblocking I/O, timeouts, poll/select readiness, address queries, and
socketpair.
Linux abstract namespace addresses and credential ancillary data are not
supported. The component requires DFSv2.
+12
View File
@@ -0,0 +1,12 @@
from building import *
cwd = GetCurrentDir()
src = Glob('src/*.c')
CPPPATH = [cwd + '/include', cwd + '/src']
if GetDepend('RT_AF_UNIX_USING_TESTCASES'):
src += Glob('testcases/*.c')
group = DefineGroup('AF_UNIX', src, depend=['RT_USING_AF_UNIX'], CPPPATH=CPPPATH)
Return('group')
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2006-2026, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef AF_UNIX_H__
#define AF_UNIX_H__
#ifdef __cplusplus
extern "C" {
#endif
int af_unix_init(void);
#ifdef __cplusplus
}
#endif
#endif /* AF_UNIX_H__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,164 @@
/*
* Copyright (c) 2006-2026, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef AF_UNIX_INTERNAL_H__
#define AF_UNIX_INTERNAL_H__
#include <rtthread.h>
#include <dfs.h>
#include <dfs_file.h>
#include <poll.h>
#include <sal_low_lvl.h>
#include <sal_socket.h>
#ifndef AF_UNIX_DGRAM_MAX_SIZE
#define AF_UNIX_DGRAM_MAX_SIZE 4096
#endif
#ifndef AF_UNIX_DGRAM_QUEUE_LEN
#define AF_UNIX_DGRAM_QUEUE_LEN 16
#endif
#ifndef AF_UNIX_STREAM_BUFFER_SIZE
#define AF_UNIX_STREAM_BUFFER_SIZE 4096
#endif
#ifndef AF_UNIX_LISTEN_BACKLOG_MAX
#define AF_UNIX_LISTEN_BACKLOG_MAX 16
#endif
#ifndef AF_UNIX_RIGHTS_MAX
#define AF_UNIX_RIGHTS_MAX 16
#endif
#define AF_UNIX_PATH_MAX ((int)sizeof(((struct sockaddr_un *)0)->sun_path))
struct af_unix_rights
{
rt_list_t node;
rt_uint64_t offset;
size_t count;
struct dfs_file *files[1];
};
struct af_unix_message
{
rt_list_t node;
size_t length;
struct sockaddr_un source;
socklen_t source_length;
struct af_unix_rights *rights;
char data[1];
};
struct af_unix_socket
{
int handle;
int type;
int flags;
int ref_count;
int closed;
int bound;
int connected;
int listening;
int read_shutdown;
int write_shutdown;
int socket_error;
int receive_timeout;
int send_timeout;
struct sockaddr_un local_address;
socklen_t local_length;
struct sockaddr_un peer_address;
socklen_t peer_length;
struct af_unix_socket *peer;
void *namespace_entry;
rt_wqueue_t wait_queue;
rt_list_t message_queue;
rt_size_t message_count;
char *stream_buffer;
rt_size_t stream_head;
rt_size_t stream_length;
rt_uint64_t stream_read_offset;
rt_uint64_t stream_write_offset;
rt_list_t rights_queue;
rt_list_t pending_queue;
rt_list_t pending_node;
int pending;
int backlog;
int pending_count;
};
void af_unix_lock(void);
void af_unix_unlock(void);
int af_unix_error(int error);
int af_unix_is_nonblocking(const struct af_unix_socket *sock, int flags);
int af_unix_wait(rt_wqueue_t *queue, int timeout);
int af_unix_rights_create(const struct msghdr *message,
struct af_unix_rights **rights);
void af_unix_rights_release(struct af_unix_rights *rights);
void af_unix_rights_list_release(rt_list_t *list);
void af_unix_rights_defer_locked(struct af_unix_rights *rights);
void af_unix_rights_list_defer_locked(rt_list_t *list);
void af_unix_rights_drain(void);
int af_unix_rights_deliver(rt_list_t *list, struct msghdr *message);
int af_unix_rights_init(void);
struct af_unix_socket *af_unix_socket_create_locked(int type);
struct af_unix_socket *af_unix_socket_get(int handle);
void af_unix_socket_ref_locked(struct af_unix_socket *sock);
void af_unix_socket_unref_locked(struct af_unix_socket *sock);
void af_unix_socket_put(struct af_unix_socket *sock);
int af_unix_handle_alloc_locked(struct af_unix_socket *sock);
void af_unix_handle_remove_locked(struct af_unix_socket *sock);
void af_unix_socket_close_locked(struct af_unix_socket *sock);
void af_unix_wakeup_writable_locked(struct af_unix_socket *sock);
int af_unix_connect_peers_locked(struct af_unix_socket *first,
struct af_unix_socket *second);
void af_unix_set_peer_locked(struct af_unix_socket *sock,
struct af_unix_socket *peer);
int af_unix_address_parse(const struct sockaddr *address, socklen_t length,
char path[AF_UNIX_PATH_MAX]);
void af_unix_address_set(struct sockaddr_un *address, socklen_t *length,
const char *path);
int af_unix_address_copy(struct sockaddr *address, socklen_t *length,
const struct sockaddr_un *source,
socklen_t source_length);
int af_unix_namespace_bind(struct af_unix_socket *sock,
const struct sockaddr *address, socklen_t length);
struct af_unix_socket *af_unix_namespace_lookup(
const struct sockaddr *address, socklen_t length, int type,
struct sockaddr_un *normalized_address, socklen_t *normalized_length);
void af_unix_namespace_detach_locked(struct af_unix_socket *sock);
int af_unix_namespace_init(void);
int af_unix_dgram_connect(struct af_unix_socket *sock,
const struct sockaddr *address, socklen_t length);
int af_unix_dgram_send(struct af_unix_socket *sock, const void *data,
size_t size, int flags, const struct sockaddr *to,
socklen_t to_length, struct af_unix_rights *rights);
int af_unix_dgram_receive(struct af_unix_socket *sock, void *data,
size_t size, int flags, struct sockaddr *from,
socklen_t *from_length, rt_list_t *rights);
int af_unix_stream_listen(struct af_unix_socket *sock, int backlog);
int af_unix_stream_connect(struct af_unix_socket *sock,
const struct sockaddr *address, socklen_t length);
int af_unix_stream_accept(struct af_unix_socket *sock,
struct sockaddr *address, socklen_t *length);
int af_unix_stream_send(struct af_unix_socket *sock, const void *data,
size_t size, int flags,
struct af_unix_rights *rights);
int af_unix_stream_receive(struct af_unix_socket *sock, void *data,
size_t size, int flags, rt_list_t *rights);
int af_unix_stream_shutdown(struct af_unix_socket *sock, int how);
#endif /* AF_UNIX_INTERNAL_H__ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5
View File
@@ -48,6 +48,7 @@ typedef uint32_t socklen_t;
struct sockaddr;
struct msghdr;
struct addrinfo;
struct sal_proto_family;
struct sal_socket
{
uint32_t magic; /* SAL socket magic word */
@@ -58,6 +59,7 @@ struct sal_socket
int protocol;
struct netdev *netdev; /* SAL network interface device */
const struct sal_proto_family *protocol_family; /* selected protocol provider */
void *user_data; /* user-specific data */
#ifdef SAL_USING_TLS
@@ -109,6 +111,9 @@ struct sal_proto_family
/* SAL(Socket Abstraction Layer) initialize */
int sal_init(void);
/* Register and find protocol providers which do not require a netdev. */
int sal_proto_family_register(const struct sal_proto_family *pf);
const struct sal_proto_family *sal_proto_family_find(int family);
/* Get SAL socket object by socket descriptor */
struct sal_socket *sal_get_socket(int sock);
+3
View File
@@ -119,6 +119,9 @@ typedef uint16_t in_port_t;
#define MSG_DONTWAIT 0x08 /* Nonblocking i/o for this operation only */
#define MSG_MORE 0x10 /* Sender will send more */
/* Output-only flags returned through struct msghdr. */
#define MSG_CTRUNC 0x08 /* Control data was discarded due to truncation */
#define MSG_ERRQUEUE 0x2000 /* Fetch message from error queue */
#define MSG_CONFIRM 0x0800 /* Confirm path validity */
+20
View File
@@ -725,7 +725,11 @@ int closesocket(int s)
return -1;
}
#ifdef RT_USING_DFS_V2
if (dfs_file_close(d) == 0)
#else
if (sal_closesocket(socket) == 0)
#endif
{
error = 0;
}
@@ -771,8 +775,19 @@ RTM_EXPORT(closesocket);
int socketpair(int domain, int type, int protocol, int *fds)
{
rt_err_t ret = 0;
int nonblocking = 0;
int sock_fds[2];
if ((type & SOCK_CLOEXEC) != 0)
{
type &= ~SOCK_CLOEXEC;
}
if ((type & SOCK_NONBLOCK) != 0)
{
nonblocking = 1;
type &= ~SOCK_NONBLOCK;
}
fds[0] = socket(domain, type, protocol);
if (fds[0] < 0)
{
@@ -799,6 +814,11 @@ int socketpair(int domain, int type, int protocol, int *fds)
closesocket(fds[0]);
closesocket(fds[1]);
}
else if (nonblocking)
{
(void)fcntl(fds[0], F_SETFL, O_NONBLOCK);
(void)fcntl(fds[1], F_SETFL, O_NONBLOCK);
}
return ret;
}
File diff suppressed because it is too large Load Diff