Can I open a socket and pass it to another process in Linux
Yes you can, using sendmsg()
with SCM_RIGHTS from one process to another:
SCM_RIGHTS
- Send or receive a set of
open file descriptors from another
process. The data portion contains an
integer array of the file descriptors.
The passed file descriptors behave as
though they have been created with
dup(2)
.
http://linux.die.net/man/7/unix
That is not the typical usage though. More common is when a process inherits sockets from its parent (after a fork()
). Any file handles (including sockets) not closed will be available to the child process. So the child process inherits the parent's sockets.
A server process that listens for connections is called a daemon. This usually forks on each new connection, spawning a process to handle each new request. An example of the typical daemon is here:
http://www.steve.org.uk/Reference/Unix/faq_8.html#SEC88
Scroll down to void process()
.
transfer socket between processes in Linux
The child process will inherit the file descriptor. So you have nothing to do except closing the socket in the parent after you forked the child.
If you exec
another executable in the child, you may want to inform it of the file descriptor value by using a specific argument.
Share socket between unrelated processes like systemd
systemd
is not unrelated to the processes who share the sockets. systemd
starts up and supervises the entire system, so it can pass the socket file descriptors during exec()
easily. systemd
listens on behalf of the services and whenever a connection would come in, an instance of the respective service would be spawned. Here is the implementation:
int main(int argc, char **argv, char **envp) {
int r, n;
int epoll_fd = -1;
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r <= 0)
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
r = install_chld_handler();
if (r < 0)
return EXIT_FAILURE;
n = open_sockets(&epoll_fd, arg_accept);
if (n < 0)
return EXIT_FAILURE;
if (n == 0) {
log_error("No sockets to listen on specified or passed in.");
return EXIT_FAILURE;
}
for (;;) {
struct epoll_event event;
r = epoll_wait(epoll_fd, &event, 1, -1);
if (r < 0) {
if (errno == EINTR)
continue;
log_error_errno(errno, "epoll_wait() failed: %m");
return EXIT_FAILURE;
}
log_info("Communication attempt on fd %i.", event.data.fd);
if (arg_accept) {
r = do_accept(argv[optind], argv + optind, envp, event.data.fd);
if (r < 0)
return EXIT_FAILURE;
} else
break;
}
...
}
Once a connection comes in, it will call do_accept()
:
static int do_accept(const char* name, char **argv, char **envp, int fd) {
_cleanup_free_ char *local = NULL, *peer = NULL;
_cleanup_close_ int fd_accepted = -1;
fd_accepted = accept4(fd, NULL, NULL, 0);
if (fd_accepted < 0)
return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd);
getsockname_pretty(fd_accepted, &local);
getpeername_pretty(fd_accepted, true, &peer);
log_info("Connection from %s to %s", strna(peer), strna(local));
return fork_and_exec_process(name, argv, envp, fd_accepted);
}
finally, it calls execvpe(name, argv, envp);
and wrap the fd up in envp
. There is a trick in it, if fd_accepted
is not equal to SD_LISTEN_FDS_START
, it call dup2()
to makes SD_LISTEN_FDS_START
be the copy of fd_accepted
:
if (start_fd != SD_LISTEN_FDS_START) {
assert(n_fds == 1);
r = dup2(start_fd, SD_LISTEN_FDS_START);
if (r < 0)
return log_error_errno(errno, "Failed to dup connection: %m");
safe_close(start_fd);
start_fd = SD_LISTEN_FDS_START;
}
So you can just use file descriptor 3 like this in your application, sd_listen_fds
will parse the environment variable LISTEN_FDS
passed from envp
:
int listen_sock;
int fd_count = sd_listen_fds(0);
if (fd_count == 1) { // assume one socket only
listen_sock = SD_LISTEN_FDS_START; // SD_LISTEN_FDS_START is a macro defined to 3
} else {
// error
}
struct sockaddr addr;
socklen_t addrlen;
while (int client_sock = accept(listen_sock, &addr, &addrlen)) {
// do something
}
How can I pass a socket from parent to child processes
When you call fork, the child process inherits copies of all open file descriptors. The typical way of doing this is for a parent process to open a listening socket, call accept which blocks until a connection arrives and then calls fork after receiving the connection. The parent then closes it's copy of the file descriptor, while the new child process can keep using the file descriptor and do any processing which is needed. Once the child is done it also closes the socket. It's important to remember two things: 1. The file descriptor / socket is a resource in the operating system and after the fork the parent and child each have a handle to that resource, which is kind of like a reference counted smart pointer. I explain this in more detail here. The second thing is that only file descriptors which are opened before calling fork are shared, because after forking parent and child are completely separate processes, even though they may share some resources like file descriptors which existed prior to the fork. If you're using a model where you want to have a parent handing out work to worker processes, it may be better for you to consider using threads, and a thread pool.
By the way, you can download allot of nice examples of servers and clients from Unix Network Programming website.
Passing socket descriptor between two processes using shared memory
No, you can't just use some alternate method to transfer the same "stuff" that would have gone into the sendmsg
call. When you "pass a file descriptor", what you're really transferring is access to the kernel-internal file object.
The cmsg structure is just a way of formatting a request to the kernel, in which you say "I want to duplicate this open file object, and allow the process that reads this socket to gain access to it". The name SCM_RIGHTS
is a clue that what you're transferring is in essence a permission.
Since the request is for manipulation of a kernel-internal object with security implications, you can't sneak around it. You have to make a syscall. And sendmsg
is it. (There have been other fd-passing APIs... something with Streams on SysV I think. I don't know if that one is still alive in any recent OSes. For BSD and Linux at least, sendmsg
with SCM_RIGHTS
is the way to go.)
In general, this is exactly the difference between msg and cmsg: cmsg is used for operations where the kernel is doing more than just copying some bytes from one end of the socket to the other.
how to pass a fd to another process?
Open file descriptors are inherited when using fork. There is nothing you should.
From fork manpage:
The child inherits copies of the parent's set of open file
descriptors. Each file descriptor in the child refers to the same
open file description (see open(2)) as the corresponding file
descriptor in the parent. This means that the two file
descriptors share open file status flags, file offset, and signal-
driven I/O attributes (see the description of F_SETOWN and
F_SETSIG in fcntl(2)).
As for exec that still holds true (if you didn't mark the fd as close-on-exec). From execve man page (all exec* calls are just a wrapper around this system call):
By default, file descriptors remain open across an execve(). File descriptors that are marked close-on-exec are closed; see the description of FD_CLOEXEC in fcntl(2).
Can I share a file descriptor to another process on linux or are they local to the process?
You can pass a file descriptor to another process over unix domain sockets.
Here's the code to pass such a file descriptor, taken from Unix Network Programming
ssize_t
write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
{
struct msghdr msg;
struct iovec iov[1];
#ifdef HAVE_MSGHDR_MSG_CONTROL
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
} control_un;
struct cmsghdr *cmptr;
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(int));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
*((int *) CMSG_DATA(cmptr)) = sendfd;
#else
msg.msg_accrights = (caddr_t) &sendfd;
msg.msg_accrightslen = sizeof(int);
#endif
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov[0].iov_base = ptr;
iov[0].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
return(sendmsg(fd, &msg, 0));
}
/* end write_fd */
And here's the code to receive the file descriptor
ssize_t
read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
struct msghdr msg;
struct iovec iov[1];
ssize_t n;
int newfd;
#ifdef HAVE_MSGHDR_MSG_CONTROL
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
} control_un;
struct cmsghdr *cmptr;
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
#else
msg.msg_accrights = (caddr_t) &newfd;
msg.msg_accrightslen = sizeof(int);
#endif
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov[0].iov_base = ptr;
iov[0].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
if ( (n = recvmsg(fd, &msg, 0)) <= 0)
return(n);
#ifdef HAVE_MSGHDR_MSG_CONTROL
if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
if (cmptr->cmsg_level != SOL_SOCKET)
err_quit("control level != SOL_SOCKET");
if (cmptr->cmsg_type != SCM_RIGHTS)
err_quit("control type != SCM_RIGHTS");
*recvfd = *((int *) CMSG_DATA(cmptr));
} else
*recvfd = -1; /* descriptor was not passed */
#else
/* *INDENT-OFF* */
if (msg.msg_accrightslen == sizeof(int))
*recvfd = newfd;
else
*recvfd = -1; /* descriptor was not passed */
/* *INDENT-ON* */
#endif
return(n);
}
/* end read_fd */
Related Topics
How to Copy Commits from One Git Repo to Another
How to Change 'Rpath' in an Already Compiled Binary
How to Install Latest Version of Git on Centos 8.X/7.X/6.X
Appending a Line to a File Only If It Does Not Already Exist
Is There a "Goto" Statement in Bash
How to Compile a 32-Bit Binary on a 64-Bit Linux Machine With Gcc/Cmake
How to Replace Spaces in File Names Using a Bash Script
How to Remove ^[, and All of the Escape Sequences in a File Using Linux Shell Scripting
How Are the Fs/Gs Registers Used in Linux Amd64
The Difference Between Fork(), Vfork(), Exec() and Clone()
How to Know the Script File Name in a Bash Script
What Is a .Pid File and What Does It Contain