Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions Sources/CSocket/ancillary.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//
// ancillary.c
// Socket
//
// SCM_RIGHTS file descriptor passing. See CSocketAncillary.h.
//

#include "CSocketAncillary.h"

#if defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__)

#include <string.h>
#include <errno.h>
#include <unistd.h>

ssize_t c_socket_send_descriptors(int socket,
const void *buffer,
size_t length,
const int *descriptors,
size_t count,
int flags)
{
if (count > C_SOCKET_MAX_DESCRIPTORS) {
errno = EINVAL;
return -1;
}

// A message with no payload may be dropped before its ancillary data is seen, so the
// caller must always send at least one byte alongside the descriptors.
if (length == 0) {
errno = EINVAL;
return -1;
}

struct iovec iov;
iov.iov_base = (void *)buffer;
iov.iov_len = length;

struct msghdr message;
memset(&message, 0, sizeof(message));
message.msg_iov = &iov;
message.msg_iovlen = 1;

// Sized for the worst case so the buffer is a fixed, stack allocated size.
char control[CMSG_SPACE(sizeof(int) * C_SOCKET_MAX_DESCRIPTORS)];

if (count > 0) {

memset(control, 0, sizeof(control));

message.msg_control = control;
message.msg_controllen = CMSG_SPACE(sizeof(int) * count);

struct cmsghdr *header = CMSG_FIRSTHDR(&message);
header->cmsg_level = SOL_SOCKET;
header->cmsg_type = SCM_RIGHTS;
header->cmsg_len = CMSG_LEN(sizeof(int) * count);

memcpy(CMSG_DATA(header), descriptors, sizeof(int) * count);

// msg_controllen must match what was actually written.
message.msg_controllen = header->cmsg_len;
}

return sendmsg(socket, &message, flags);
}

ssize_t c_socket_receive_descriptors(int socket,
void *buffer,
size_t length,
int *descriptors,
size_t capacity,
size_t *received_count,
int *truncated,
int flags)
{
*received_count = 0;
*truncated = 0;

struct iovec iov;
iov.iov_base = buffer;
iov.iov_len = length;

char control[CMSG_SPACE(sizeof(int) * C_SOCKET_MAX_DESCRIPTORS)];
memset(control, 0, sizeof(control));

struct msghdr message;
memset(&message, 0, sizeof(message));
message.msg_iov = &iov;
message.msg_iovlen = 1;
message.msg_control = control;
message.msg_controllen = sizeof(control);

ssize_t result = recvmsg(socket, &message, flags);

if (result < 0) {
return result;
}

// The kernel had more ancillary data than fitted; any descriptors it did deliver are still
// handled below, and the caller is told the set is incomplete.
if (message.msg_flags & MSG_CTRUNC) {
*truncated = 1;
}

for (struct cmsghdr *header = CMSG_FIRSTHDR(&message);
header != NULL;
header = CMSG_NXTHDR(&message, header)) {

if (header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS) {
continue;
}

size_t payload = header->cmsg_len - CMSG_LEN(0);
size_t available = payload / sizeof(int);

const int *incoming = (const int *)(const void *)CMSG_DATA(header);

for (size_t index = 0; index < available; index++) {

if (descriptors != NULL && *received_count < capacity) {
descriptors[*received_count] = incoming[index];
(*received_count)++;
} else {
// Close rather than leak a descriptor the caller cannot accept.
close(incoming[index]);
*truncated = 1;
}
}
}

return result;
}

#endif
66 changes: 66 additions & 0 deletions Sources/CSocket/include/CSocketAncillary.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// CSocketAncillary.h
// Socket
//
// Passing file descriptors over a Unix domain socket with SCM_RIGHTS.
//
// The CMSG_* accessors are C macros and so cannot be called from Swift. Rather than
// reimplement their alignment arithmetic, these shims perform the whole exchange in C.
//

#ifndef CSocketAncillary_h
#define CSocketAncillary_h

#if defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__)

#include <sys/types.h>
#include <sys/socket.h>
#include <stddef.h>

/// Send a message carrying file descriptors as SCM_RIGHTS ancillary data.
///
/// @param socket The socket to send on. Must be a Unix domain socket.
/// @param buffer The message payload. At least one byte must be sent, because a message
/// with no payload may be discarded before its ancillary data is delivered.
/// @param length The payload length in bytes.
/// @param descriptors The descriptors to send, or NULL when sending none.
/// @param count How many descriptors to send.
/// @param flags Flags for sendmsg().
/// @return The number of payload bytes sent, or -1 with errno set.
ssize_t c_socket_send_descriptors(int socket,
const void *buffer,
size_t length,
const int *descriptors,
size_t count,
int flags);

/// Receive a message and any SCM_RIGHTS file descriptors that accompany it.
///
/// Descriptors beyond `capacity` are closed rather than leaked, and `truncated` reports it.
///
/// @param socket The socket to receive from.
/// @param buffer Where to write the payload.
/// @param length The capacity of `buffer` in bytes.
/// @param descriptors Where to write received descriptors, or NULL to accept none.
/// @param capacity How many descriptors `descriptors` can hold.
/// @param received_count Set to the number of descriptors written. Must not be NULL.
/// @param truncated Set to 1 if descriptors had to be discarded. Must not be NULL.
/// @param flags Flags for recvmsg().
/// @return The number of payload bytes received, 0 at end of stream, or -1 with errno set.
ssize_t c_socket_receive_descriptors(int socket,
void *buffer,
size_t length,
int *descriptors,
size_t capacity,
size_t *received_count,
int *truncated,
int flags);

/// The maximum number of descriptors a single message may carry.
///
/// Matches the kernel's SCM_MAX_FD, and bounds the ancillary buffer these shims allocate.
#define C_SOCKET_MAX_DESCRIPTORS 253

#endif

#endif /* CSocketAncillary_h */
1 change: 1 addition & 0 deletions Sources/CSocket/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ module CSocket {
header "CSystemWASI.h"
header "CSystemWindows.h"
header "CSystemAndroid.h"
header "CSocketAncillary.h"
export *
}
Loading
Loading