File descriptor passing with sendmsg(2) and recvmsg(2) over Unix domain socket

Overview

Two processes connected with Unix domain socket can share file descriptors with sendmsg(2) and recvmsg(2) even if the file descriptors are created after fork(2).

This article is for FreeBSD, but I guess that this method is available on Linux.

Description

If two or more processes want to share a file descriptor, the most usual way is fork(2) after creating the file descriptor with open(2), pipe(2) and so on.

But when the processes are connected with Unix domain socket, you can use sendmsg(2) and recvmsg(2), too. If you use these system calls, you can open the file to share after calling fork(2).

What you need to send the file descriptor are:

  1. giving SOL_SOCKET to cmsg_level of a struct cmsghdr
  2. giving SCM_RIGHTS to cmsg_type of the struct cmsghdr
  3. placing the file descriptor at last of the struct cmsghdr
  4. assigning the struct cmsghdr to msg_control of a struct msghdr
  5. passing the struct msghdr to sendmsg(2) with the Unix domain socket

Using CMSG_DATA is easy to get a pointer to last of the struct cmsghdr for 3.

To have the file descriptor, you must call recvmsg(2) with a struct msghdr pointing a struct cmsghdr including SOL_SOCKET, SCM_RIGHTS and space to store the file descriptor. If recvmsg(2) successes, you will have the file descriptor at this space.

Sample code

This code is also available in Gist.

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define	SIG	SIGUSR1

static void
diec(int error, const char *msg)
{

	fprintf(stderr, "%s: %s\n", msg, strerror(error));
	exit(1);
}

static void
die(const char *msg)
{

	diec(errno, msg);
}

#if 0
static void
print_alive_fd(const char *tag)
{
	struct stat sb;
	int i;

	for (i = 0; i < 32; i++) {
		if (fstat(i, &sb) == -1)
			continue;
		printf("%s: fd %d is alive.\n", tag, i);
	}
}
#endif

static int
do_sendmsg(int sock, const char *sockpath, int fd)
{
	struct msghdr msg;
	struct cmsghdr *cmsghdr;
	struct iovec iov[1];
	ssize_t nbytes;
	int i, *p;
	char buf[CMSG_SPACE(sizeof(int))], c;

	c = '*';
	iov[0].iov_base = &c;
	iov[0].iov_len = sizeof(c);
	memset(buf, 0x0b, sizeof(buf));
	cmsghdr = (struct cmsghdr *)buf;
	cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));
	cmsghdr->cmsg_level = SOL_SOCKET;
	cmsghdr->cmsg_type = SCM_RIGHTS;
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
	msg.msg_control = cmsghdr;
	msg.msg_controllen = CMSG_LEN(sizeof(int));
	msg.msg_flags = 0;
	p = (int *)CMSG_DATA(buf);
	*p = fd;
	printf("sendmsg: %d\n", fd);

	nbytes = sendmsg(sock, &msg, 0);
	if (nbytes == -1)
		return (1);

	return (0);
}

static int
server_main(pid_t ppid, const char *sockpath)
{
	struct sockaddr_storage storage;
	struct sockaddr_un *addr;
	int error, fd, s, sock;
	const char *filepath = "fd_passing.txt";

	sock = socket(PF_LOCAL, SOCK_STREAM, 0);
	if (sock == -1)
		die("socket(2)");
	addr = (struct sockaddr_un *)&storage;
	addr->sun_family = AF_LOCAL;
	strlcpy(addr->sun_path, sockpath, sizeof(addr->sun_path));
	addr->sun_len = SUN_LEN(addr);
	if (bind(sock, (struct sockaddr *)addr, addr->sun_len) == -1)
		die("bind(2)");
	if (listen(sock, 0) == -1)
		goto fail;
	if (kill(ppid, SIG) == -1)
		goto fail;
	if ((s = accept(sock, NULL, 0)) == -1)
		goto fail;
	if (unlink(filepath) == -1)
		goto fail;
	if ((fd = open(filepath, O_WRONLY | O_CREAT, 0644)) == -1)
		goto fail;

	if (do_sendmsg(s, sockpath, fd) != 0)
		goto fail;

	if (close(fd) == -1)
		goto fail;
	if (close(s) == -1)
		goto fail;
	if (close(sock) == -1)
		goto fail;
	if (unlink(sockpath) == -1)
		goto fail;

	return (0);

fail:
	error = errno;
	unlink(sockpath);
	diec(error, "");

	/* NOTREACHED */
	return (1);
}

static int
do_recvmsg(int sock)
{
	struct msghdr msg;
	struct cmsghdr *cmsghdr;
	struct iovec iov[1];
	FILE *fp;
	ssize_t nbytes;
	int i, *p;
	char buf[CMSG_SPACE(sizeof(int))], c;

	iov[0].iov_base = &c;
	iov[0].iov_len = sizeof(c);
	memset(buf, 0x0d, sizeof(buf));
	cmsghdr = (struct cmsghdr *)buf;
	cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));
	cmsghdr->cmsg_level = SOL_SOCKET;
	cmsghdr->cmsg_type = SCM_RIGHTS;
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = iov;
	msg.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
	msg.msg_control = cmsghdr;
	msg.msg_controllen = CMSG_LEN(sizeof(int));
	msg.msg_flags = 0;

	nbytes = recvmsg(sock, &msg, 0);
	if (nbytes == -1)
		return (1);

	p = (int *)CMSG_DATA(buf);
	printf("recvmsg: %d\n", *p);
	fp = fdopen(*p, "w");
	fprintf(fp, "OK\n");
	fclose(fp);

	return (0);
}

static int
client_main(pid_t pid, const char *sockpath)
{
	struct sockaddr_storage sockaddr;
	struct sockaddr_un *addr;
	sigset_t set;
	int sig, sock, status;
	const char *signame;

	if (sigemptyset(&set) == -1)
		die("sigemptyset(3)");
	if (sigaddset(&set, SIG) == -1)
		die("sigaddset(3)");
	if (sigwait(&set, &sig) != 0)
		die("sigwait(2)");
	if (sig != SIG)
		return (2);
	sock = socket(PF_LOCAL, SOCK_STREAM, 0);
	if (sock == -1)
		die("socket(2)");
	addr = (struct sockaddr_un *)&sockaddr;
	addr->sun_family = AF_LOCAL;
	strlcpy(addr->sun_path, sockpath, sizeof(addr->sun_path));
	addr->sun_len = SUN_LEN(addr);
	if (connect(sock, (struct sockaddr *)addr, addr->sun_len) == -1)
		die("connect(2)");

	if (do_recvmsg(sock) == -1)
		return (3);

	if (close(sock) == -1)
		die("close(2)");
	if (wait4(pid, &status, 0, NULL) == -1)
		die("wait4(2)");
	if (!WIFEXITED(status))
		return (4);
	if (WEXITSTATUS(status) != 0)
		return (32 + WEXITSTATUS(status));

	return (0);
}

int
main(int argc, const char *argv[])
{
	sigset_t set;
	pid_t pid, ppid;
	char sockpath[MAXPATHLEN];

	if (sigfillset(&set) == -1)
		die("sigfillset(3)");
	if (sigprocmask(SIG_BLOCK, &set, NULL) == -1)
		die("sigprocmask(2)");

	ppid = getpid();
	snprintf(sockpath, sizeof(sockpath), "%d.sock", ppid);

	pid = fork();
	switch (pid) {
	case -1:
		die("fork(2)");
	case 0:
		return (server_main(ppid, sockpath));
	default:
		break;
	}

	return (client_main(pid, sockpath));
}

This program fork(2)s. The child process will make a Unix domain socket, and the parent process will connect to it.

The child process opens a file (fd_passing.txt) after the fork(2). The process will call sendmsg(2) to send the file descriptor to the parent over the Unix domain socket. The parent will write message (“OK”) to the file after receiving the file descriptor.

This program will output like:

sendmsg: 5
recvmsg: 4

This means that the child process sent a file descriptor of 5, and the parent received it as a file descriptor of 4.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s