I am trying to share a socket descriptor with another local process on the same Linux machine. These processes are "unrelated" i.e., they are not parent/child related and are not forked. They are independent. Ultimately, the flow I want is something like:
| [Process1]
| -> master_socket = socket()
| -> setsockopt(master_socket...)
| -> fcntl(master_socket...)
| -> bind(master_socket...)
| -> listen(master_socket...)
| -> Share master_socket with Process2
|
| [Process2]
| -> Receive and store master_socket
| -> Use select() on master_socket
| -> Use accept() on master_socket to receive connections...
Based on a few related threads, it seems as though this is possible using a Unix domain socket which will track that the socket handle was sent from Process1 to Process2 in the kernel, giving it permission (e.g., here, here, and here).
What I am trying to determine is whether or not the descriptor is possible to share over a POSIX message queue. Oddly enough, if I create the socket BEFORE opening the queue, it seems to work OK. However, if I create the socket AFTER opening the queue, the descriptor read on Process2 comes up as "invalid."
Example Programs
Here is a sample program that sends a socket descriptor via a message queue. If init_socket() is called before open_queue(), then the descriptor received is valid. If visa-versa then it comes across invalid.
send.c: gcc -o send send.c -lrt
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <syslog.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/fcntl.h>
#include <string.h>
#include <errno.h>
#include <mqueue.h>
int init_socket();
int open_queue();
mqd_t msgq_id;
int main() {
int socket;
char msg_str[16];
// HERE: ordering matters. If open_queue() is done before init_socket(), then
// the descriptor is received invalid. If init_socket() is called BEFORE open_queue()
// then the descriptor received is valid.
open_queue();
socket = init_socket();
// Put the socket on the queue
memset(msg_str, '\0', sizeof(msg_str));
snprintf(msg_str, sizeof(msg_str), "%d", socket);
if(mq_send(msgq_id, msg_str, strlen(msg_str)+1, 1) == -1) {
printf("Unable to send the message on the queue: %s\n", strerror(errno));
return -1;
}
}
int open_queue() {
// Create a queue to share the socket
if(msgq_id = mq_open("/share_socket", O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG, NULL)==-1) {
if(errno != EEXIST) {
printf("Failed to create IPC queue: %s\n", strerror(errno));
return -1;
}
// Re-open the already existing queue.
if((msgq_id = mq_open("/share_socket", O_RDWR)) != -1) {
printf("Re-opened the IPC queue: %s\n", "/share_socket");
} else {
printf("Failed to re-open IPC queue %s: %s\n", "/share_socket", strerror(errno));
return -1;
}
}
return 1;
}
int init_socket() {
int master_socket;
int opt=1;
struct sockaddr_in loc_addr = { 0 };
// Create the high level master socket
if( (master_socket = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
printf("Unable to create master_socket\n");
return EXIT_FAILURE;
}
// Set socket to accept multiple connections
if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 ) {
printf("Error setting socket to accept multiple connections\n");
return EXIT_FAILURE;
}
// Set the socket type
bzero(&loc_addr, sizeof(struct sockaddr_in));
loc_addr.sin_family = AF_INET;
loc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
loc_addr.sin_port=htons(1200);
// Set the socket to nonblocking
if (fcntl(master_socket, F_SETFL, O_NDELAY) < 0) {
printf("Can't set socket to non-blocking\n");
return EXIT_FAILURE;
}
// Bind to the socket
if (bind(master_socket, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) {
return EXIT_FAILURE;
}
// Now, listen for a maximum of 6 pending clients
if(listen(master_socket, 6) < 0) {
printf("Could not set the socket to listen\n");
close(master_socket);
return EXIT_FAILURE;
}
return master_socket;
}
read.c: gcc -o read read.c -lrt
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <syslog.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/fcntl.h>
#include <string.h>
#include <errno.h>
#include <mqueue.h>
#define MAX_MSG_LEN 10000
int main() {
mqd_t msgq_id;
int master_socket;
unsigned int sender;
int bytes;
char msgcontent[MAX_MSG_LEN];
// Re-open the already existing queue.
if((msgq_id = mq_open("/share_socket", O_RDWR)) != -1) {
printf("Re-opened the IPC queue: %s\n", "/share_socket");
} else {
printf("Failed to re-open IPC queue %s: %s\n", "/share_socket", strerror(errno));
return -1;
}
// Try to read from the queue.
if((bytes = mq_receive(msgq_id, msgcontent, MAX_MSG_LEN, &sender)) == -1)
{
// If the failure was due to there being no messages, just return 0.
if(errno==EAGAIN)
return 0;
printf("Unable to read from the queue\n");
return -1;
}
sscanf(msgcontent, "%d", &master_socket);
printf("Got master socket value: %d\n", master_socket);
if(master_socket != 0 && (fcntl(master_socket, F_GETFD) != -1 || errno != EBADF))
printf("... socket is valid\n");
else
printf("... SOCKET IS INVALID\n");
return 1;
}
How to run: Just go ahead and run ./read
and it will wait on the message queue and then run ./send
. If you compiled send.c with init_socket() before open_queue(), you will see:
$ ./read
Re-opened the IPC queue: /share_socket
Got master socket value: 3
... socket is valid
Otherwise:
$ ./read
Re-opened the IPC queue: /share_socket
Got master socket value: 4
... SOCKET IS INVALID
What would cause this behavior where the ordering is important?