Unix Domain Protocol Programming: Passing Arbitrary Interprocess Descriptors

    Unix domain sockets can be used to pass descriptors between different processes on the same host, and it can be seen as one of the IPC methods. It can pass any type of descriptor between processes, which is what we call "descriptor passing", not "file descriptor passing". Descriptors are sent through auxiliary data (the msg_control member of the structure msghdr). When sending and receiving descriptors, always send at least 1 byte of data, even if this data has no practical significance. Otherwise when the receive returns 0, the receiver won't be able to distinguish whether it means "no data" (but there may be sockets for auxiliary data) or "end of file"

    In the previous " linux online debugging camera driver ", we opened the device file descriptor of the camera in the process of image transmission for real-time image transmission. Then we create a new process for debugging, send the debugging information to the image transmission process through the message queue, and then send the control command to the driver through the process. Here we have another way to achieve this function, which is to send the camera device file descriptor directly to the debugging process, and operate the camera directly in the debugging process. The following code is an instance of the child process' descriptor passed to the parent process.

/*=============================================================================
#     FileName: unixdomain.c
#         Desc: child process send describe to father process
#       Author: licaibiao
#   LastChange: 2017-02-14
=============================================================================*/
#include<stdio.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<unistd.h>  
#include<stdlib.h>  
#include<errno.h>  
#include<arpa/inet.h>  
#include<netinet/in.h>  
#include<string.h>  
#include<signal.h>
#include<fcntl.h>
#include<sys/un.h>

#define MAXLINE	  1024
#define LISTENLEN 10
#define HAVE_MSGHDR_MSG_CONTROL

void sig_chld(int signo)
{
	pid_t	pid;
	int 	stat;
	while ((pid = waitpid(-1,stat,WNOHANG))>0)
	{	
		//printf("child %d terminated \n",pid);
	}
	return ;
}

/*
@fd : ​​send TCP socket interface; this can be the send socket interface returned by socketpair
@ptr : buffer pointer to send data;
@nbytes : the number of bytes sent;
@sendfd : the descriptor sent to the receiving process;
*/
int write_fd(int fd, void *ptr, int nbytes, int sendfd)
{
    struct msghdr msg;
    struct iovec iov[1];
    // Some systems use the old msg_accrights field to pass descriptors, under Linux the new msg_control field
#ifdef HAVE_MSGHDR_MSG_CONTROL
    union{ // As mentioned earlier, ensure the alignment of cmsghdr and msg_control
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    }control_a;
    struct cmsghdr *cmptr;
    // set auxiliary buffer and length
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    // Only one set of auxiliary data is enough, obtained directly through CMSG_FIRSTHDR
    cmptr = CMSG_FIRSTHDR(&msg);
    // set the necessary fields, data and length
    cmptr->cmsg_len = CMSG_LEN(sizeof(int)); // fd type is int, set length
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS; // Indicates that the descriptor is sent
    *((int*)CMSG_DATA(cmptr)) = sendfd; // write fd to auxiliary data
#else
    msg.msg_accrights = (caddr_t)&sendfd; // This old one is more convenient
    msg.msg_accrightslen = sizeof(int);
#endif
    // Only needed for UDP, ignore
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    // Don't forget to set the data buffer, actually 1 byte is enough
    iov [0] .iov_base = ptr;
    iov [0] .iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    return sendmsg(fd, &msg, 0);
}

int read_fd(int fd, void *ptr, int nbytes, int *recvfd)
{
    struct msghdr msg;
    struct iovec iov[1];
    int n;
    int newfd;
#ifdef HAVE_MSGHDR_MSG_CONTROL
    union{ // align
	struct cmsghdr cm;
	char control[CMSG_SPACE(sizeof(int))];
    }control_a;
    struct cmsghdr *cmptr;
    // set auxiliary data buffer and length
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
#else
    msg.msg_accrights = (caddr_t) &newfd; // this simple
    msg.msg_accrightslen = sizeof(int);
#endif
    
    // TCP ignore
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    // set data buffer
    iov [0] .iov_base = ptr;
    iov [0] .iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    // Set up, ready to receive
    if((n = recvmsg(fd, &msg, 0)) <= 0)
    {
        return n;
    }
#ifdef HAVE_MSGHDR_MSG_CONTROL
    // Check if auxiliary data is received, and the length, recall the CMSG macro in the previous section
    cmptr = CMSG_FIRSTHDR(&msg);
    if((cmptr != NULL) && (cmptr->cmsg_len == CMSG_LEN(sizeof(int))))
    {
	// still necessary check
        if(cmptr->cmsg_level != SOL_SOCKET)
        {
            printf("control level != SOL_SOCKET/n");
            exit(-1);
        }
        if(cmptr->cmsg_type != SCM_RIGHTS)
        {
            printf("control type != SCM_RIGHTS/n");
            exit(-1);
        }
	// ok, the descriptor is here
        *recvfd = *((int*)CMSG_DATA(cmptr));
    }
    else
    {
        if(cmptr == NULL) printf("null cmptr, fd not passed./n");
        else printf("message len[%d] if incorrect./n",(int)cmptr->cmsg_len);
        *recvfd = -1; // descriptor was not passed
    }
#else
    if(msg.msg_accrightslen == sizeof(int)) *recvfd = newfd;
    else *recvfd = -1;
#endif
    return n;
}

int main(int argc, char **argv)
{
	int			writefd, readfd, sockfd[2];
	pid_t		childpid;
	char        *write_ptr, *read_ptr;
	
	write_ptr = (char*)malloc(1);
	read_ptr = (char*)malloc(1);
	signal(SIGCHLD, sig_chld);

	socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
	if ( (childpid = fork()) == 0)
	{
		close(sockfd[0]);
		writefd = open(argv[1], O_RDWR|O_CREAT|O_APPEND);
		write_fd(sockfd[1], write_ptr, 1, writefd);
	}
	
	close(sockfd[1]);
	read_fd(sockfd[0], read_ptr, 1, &readfd);
	write(readfd, argv[2], strlen(argv[2]));
	close(readfd);

	free(write_ptr);
	free(read_ptr);

	return 0;
}

   The above program opens a file in the child process, then passes the file descriptor to the parent process, and then the parent process writes data to the file. The results are as follows:

root@ubuntu:/home/share/test# gcc unixdomain.c -o unixdomain
root@ubuntu:/home/share/test# ./unixdomain /tmp/testfile licaibiao
root@ubuntu:/home/share/test# cat /tmp/testfile
licaibiao
root@ubuntu:/home/share/test#
    Create the file /tmp/testfile and write the string licabiao.


    The above program is to assign descriptors between processes. In fact, it can also transfer descriptors between processes that are not related.
Let's look at the sender program:

/*=============================================================================
#     FileName: senddescribe.c
#         Desc: send describe to other process
#       Author: licaibiao
#   LastChange: 2017-02-14
=============================================================================*/
#include<stdio.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<sys/un.h>
#include<unistd.h>  
#include<stdlib.h>  
#include<errno.h>  
#include<arpa/inet.h>  
#include<netinet/in.h>  
#include<string.h>  
#include<signal.h>
#include <unistd.h>
#include <fcntl.h>

#define MAXLINE	  1024
#define LISTENLEN 10
#define SERV_PORT 6666
#define UNIXSTR_PATH "/tmp/path"
#define HAVE_MSGHDR_MSG_CONTROL

void sig_chld(int signo)
{
	pid_t	pid;
	int 	stat;
	while ((pid = waitpid(-1,stat,WNOHANG))>0)
	{	
		//printf("child %d terminated \n",pid);
	}
	return ;
}

int write_fd(int fd, void *ptr, int nbytes, int sendfd)
{
    struct msghdr msg;
    struct iovec iov[1];
    // Some systems use the old msg_accrights field to pass descriptors, under Linux the new msg_control field
#ifdef HAVE_MSGHDR_MSG_CONTROL
    union{ // As mentioned earlier, ensure the alignment of cmsghdr and msg_control
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    }control_a;
    struct cmsghdr *cmptr;
    // set auxiliary buffer and length
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    // Only one set of auxiliary data is enough, obtained directly through CMSG_FIRSTHDR
    cmptr = CMSG_FIRSTHDR(&msg);
    // set the necessary fields, data and length
    cmptr->cmsg_len = CMSG_LEN(sizeof(int)); // fd type is int, set length
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS; // Indicates that the descriptor is sent
    *((int*)CMSG_DATA(cmptr)) = sendfd; // write fd to auxiliary data
#else
    msg.msg_accrights = (caddr_t)&sendfd; // This old one is more convenient
    msg.msg_accrightslen = sizeof(int);
#endif
    // Only needed for UDP, ignore
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    // Don't forget to set the data buffer, actually 1 byte is enough
    iov [0] .iov_base = ptr;
    iov [0] .iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    return sendmsg(fd, &msg, 0);
}

int openfile(void)
{
	int fd;
	char *write_buff = "I am Server";

	fd = open("/tmp/test1",O_RDWR|O_CREAT|O_APPEND);
	write(fd, write_buff, strlen(write_buff));
	return fd;
}


int main(int argc, char **argv)
{
	int					listenfd, connfd ,sendfd;
	pid_t				childpid;
	socket_t clilen;
	struct sockaddr_un	cliaddr, servaddr;
	char * ptr;
	
	sendfd = openfile();
	ptr	= (char*)malloc(1);
	
	listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);

	unlink(UNIXSTR_PATH);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_LOCAL;
	strcpy(servaddr.sun_path, UNIXSTR_PATH);

	bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
	listen(listenfd, LISTENLEN);
	signal(SIGCHLD, sig_chld);
	connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen);
	write_fd(connfd, ptr, 1,sendfd);
	sleep(1);
	close(sendfd);
	free(ptr);
}
Look at the program on the receiving end:

/*=============================================================================
#     FileName: recvdescribe.c
#         Desc: read describe from other process
#       Author: licaibiao
#   LastChange: 2017-02-14
=============================================================================*/
#include<stdio.h>  
#include<sys/types.h>  
#include<sys/socket.h>  
#include<sys/un.h>  
#include<unistd.h>  
#include<stdlib.h>  
#include<errno.h>  
#include<arpa/inet.h>  
#include<netinet/in.h>  
#include<string.h>  
#include<signal.h>
#define MAXLINE	  1024
#define LISTENLEN 10
#define SERV_PORT 6666
#define UNIXSTR_PATH "/tmp/path"
#define HAVE_MSGHDR_MSG_CONTROL

int read_fd(int fd, void *ptr, int nbytes, int *recvfd)
{
    struct msghdr msg;
    struct iovec iov[1];
    int n;
    int newfd;
#ifdef HAVE_MSGHDR_MSG_CONTROL
    union{ // align
	struct cmsghdr cm;
	char control[CMSG_SPACE(sizeof(int))];
    }control_a;
    struct cmsghdr *cmptr;
    // set auxiliary data buffer and length
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
#else
    msg.msg_accrights = (caddr_t) &newfd; // this simple
    msg.msg_accrightslen = sizeof(int);
#endif
    
    // TCP ignore
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    // set data buffer
    iov [0] .iov_base = ptr;
    iov [0] .iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    // Set up, ready to receive
    if((n = recvmsg(fd, &msg, 0)) <= 0)
    {
        return n;
    }
#ifdef HAVE_MSGHDR_MSG_CONTROL
    // Check if auxiliary data is received, and the length, recall the CMSG macro in the previous section
    cmptr = CMSG_FIRSTHDR(&msg);
    if((cmptr != NULL) && (cmptr->cmsg_len == CMSG_LEN(sizeof(int))))
    {
	// still necessary check
        if(cmptr->cmsg_level != SOL_SOCKET)
        {
            printf("control level != SOL_SOCKET/n");
            exit(-1);
        }
        if(cmptr->cmsg_type != SCM_RIGHTS)
        {
            printf("control type != SCM_RIGHTS/n");
            exit(-1);
        }
	// ok, the descriptor is here
        *recvfd = *((int*)CMSG_DATA(cmptr));
    }
    else
    {
        if(cmptr == NULL) printf("null cmptr, fd not passed./n");
        else printf("message len[%d] if incorrect./n", (int)cmptr->cmsg_len);
        *recvfd = -1; // descriptor was not passed
    }
#else
    if(msg.msg_accrightslen == sizeof(int)) *recvfd = newfd;
    else *recvfd = -1;
#endif
    return n;
}

int main(int argc, char **argv)
{
	int					sockfd, recvfd;
	struct sockaddr_un	servaddr;
	char * ptr;
	
	ptr = (char*)malloc(1);
	sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_LOCAL;
	strcpy(servaddr.sun_path, UNIXSTR_PATH);

	connect(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
	read_fd(sockfd, ptr, 1, &recvfd);
	write(recvfd, argv[1], strlen(argv[1]));
	close(recvfd);
	free(ptr);
	exit(0);
}

The results are as follows:

root@ubuntu:/home/share/test# ./senddescribe &
[1] 5547
root@ubuntu:/home/share/test# ./recvdescribe licaibiao
root@ubuntu:/home/share/test#
[1]+  Done                    ./senddescribe
root@ubuntu:/home/share/test#
root@ubuntu:/home/share/test#
root@ubuntu:/home/share/test# cat /tmp/test1
I am Serverlicaibiao
root@ubuntu:/home/share/test#
    It can be seen that the sender opens a file and writes the string: I am Server, and then sends the file descriptor. After the receiver receives the descriptor, it enters the string licabiao in the file, and the program test is successful. Descriptor passing may seem simple, but in practice it's not as simple as it seems.

There are the following points to note:
1. It should be noted that passing a descriptor is not passing an int descriptor number, but creating a new descriptor in the receiving process, and in the kernel's file table, it is the same as the sending process. The descriptor sent by the process points to the same item.
2 Descriptors of any type can be passed between processes, such as descriptors returned by functions such as pipe, open, mkfifo or socket, accept, etc., not limited to sockets.
3 While a descriptor is being passed (sent from the call to sendmsg to the receive from the call to recvmsg), the kernel will mark it as "in flight". During this time, even if the sender tries to close the descriptor, the kernel will keep it open for the receiving process. Sending a descriptor increments its reference count by 1.
4 Descriptors are sent through auxiliary data (the msg_control member of the structure msghdr). When sending and receiving descriptors, always send at least 1 byte of data, even if this data has no practical significance. Otherwise when receive returns 0, the receiver will not be able to distinguish whether this means "no data" (but there may be sockets for auxiliary data) or "end of file".
5 During the specific implementation, the msg_control buffer of msghdr must be aligned with the cmghdr structure. It can be seen that the implementation of the code behind uses a union structure to ensure this.




Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324756044&siteId=291194637