VARUNA JAYASIRI

@vpj

Passing File Descriptors Between Processes Using Sendmsg() and Recvmsg()

January 11, 2011

Using this technique you can pass file descriptors between processes using sendmsg() and recvmsg() functions using UNIX Domain Protocol. Any descriptor can be passed using this method not just a file descriptor.

This is quite useful when you want to balance load in a multi-core system. Other way of passing file descriptors is by forking the process, but in this case you can pass file descriptors between different processes at anytime.

Creating the UNIX Domain Protocol server

SOCKET_PATH was set to /tmp/unix_socket

   int create_server() {
    struct sockaddr_un addr;
    int fd;

    if ((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
     log_error("Failed to create server socket");
     return fd;
    }

    memset(&addr, 0, sizeof(addr));

    addr.sun_family = AF_LOCAL;
    unlink(SOCKET_PATH);
    strcpy(addr.sun_path, SOCKET_PATH);

    if (bind(fd, (struct sockaddr *) &(addr),
                                 sizeof(addr)) < 0) {
     log_error("Failed to bind server socket");
     return -1;
    }

    if (listen(fd, MAX_PENDING) < 0) {
     log_error("Failed to listen on server socket");
     return -1;
    }

    setnonblocking(fd);

    /* Add handler to handle events on fd */

    return fd;
   }

I used epoll for handling events, so adding a handler was something like this.

   hash_set(ioloop->handlers, fd, handler);

   e.data.fd = fd;
   e.events = EPOLLIN;

   if(epoll_ctl(ioloop->epfd, EPOLL_CTL_ADD, fd, &e) < 0) {
    log_error("Failed to insert handler to epoll");
    return -1;
   }

Connections to the server should be accepted with accept() similar to TCP sockets.

Connecting to the server

   int connect_server() {
    struct sockaddr_un addr;
    int fd;

    if ((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
     log_error("Failed to create client socket");
     return fd;
    }

    memset(&addr, 0, sizeof(addr));

    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path, SOCKET_PATH);

    if (connect(fd,
                (struct sockaddr *) &(addr),
                sizeof(addr)) < 0) {
     log_error("Failed to connect to server");
     return -1;
    }

    setnonblocking(fd);

    /* Add handler to handle events */

    return fd;
   }

I was adding EPOLLOUT listener to the socket whenever there were file descriptors to be passed to the other process.

Receiving a file descriptor

   static int
   recv_file_descriptor(
     int socket) /* Socket from which the file descriptor is read */
   {
    int sent_fd;
    struct msghdr message;
    struct iovec iov[1];
    struct cmsghdr *control_message = NULL;
    char ctrl_buf[CMSG_SPACE(sizeof(int))];
    char data[1];
    int res;

    memset(&message, 0, sizeof(struct msghdr));
    memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int)));

    /* For the dummy data */
    iov[0].iov_base = data;
    iov[0].iov_len = sizeof(data);

    message.msg_name = NULL;
    message.msg_namelen = 0;
    message.msg_control = ctrl_buf;
    message.msg_controllen = CMSG_SPACE(sizeof(int));
    message.msg_iov = iov;
    message.msg_iovlen = 1;

    if((res = recvmsg(socket, &message, 0)) <= 0)
     return res;

    /* Iterate through header to find if there is a file descriptor */
    for(control_message = CMSG_FIRSTHDR(&message);
        control_message != NULL;
        control_message = CMSG_NXTHDR(&message,
                                      control_message))
    {
     if( (control_message->cmsg_level == SOL_SOCKET) &&
         (control_message->cmsg_type == SCM_RIGHTS) )
     {
      return *((int *) CMSG_DATA(control_message));
     }
    }

    return -1;
   }

Sending a file descriptor

   static int
   send_file_descriptor(
     int socket, /* Socket through which the file descriptor is passed */
     int fd_to_send) /* File descriptor to be passed, could be another socket */
   {
    struct msghdr message;
    struct iovec iov[1];
    struct cmsghdr *control_message = NULL;
    char ctrl_buf[CMSG_SPACE(sizeof(int))];
    char data[1];

    memset(&message, 0, sizeof(struct msghdr));
    memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int)));

    /* We are passing at least one byte of data so that recvmsg() will not return 0 */
    data[0] = ' ';
    iov[0].iov_base = data;
    iov[0].iov_len = sizeof(data);

    message.msg_name = NULL;
    message.msg_namelen = 0;
    message.msg_iov = iov;
    message.msg_iovlen = 1;
    message.msg_controllen =  CMSG_SPACE(sizeof(int));
    message.msg_control = ctrl_buf;

    control_message = CMSG_FIRSTHDR(&message);
    control_message->cmsg_level = SOL_SOCKET;
    control_message->cmsg_type = SCM_RIGHTS;
    control_message->cmsg_len = CMSG_LEN(sizeof(int));

    *((int *) CMSG_DATA(control_message)) = fd_to_send;

    return sendmsg(socket, &message, 0);
   }
///Passing File Descriptors Between Processes Using Sendmsg() and Recvmsg() Using this technique you can pass file descriptors between processes using ``sendmsg()`` and ``recvmsg()`` functions using UNIX Domain Protocol. Any descriptor can be passed using this method not just a file descriptor. This is quite useful when you want to balance load in a multi-core system. Other way of passing file descriptors is by forking the process, but in this case you can pass file descriptors between different processes at anytime. ## Creating the UNIX Domain Protocol server ``SOCKET_PATH`` was set to ``/tmp/unix_socket`` ```c int create_server() { struct sockaddr_un addr; int fd; if ((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { log_error("Failed to create server socket"); return fd; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; unlink(SOCKET_PATH); strcpy(addr.sun_path, SOCKET_PATH); if (bind(fd, (struct sockaddr *) &(addr), sizeof(addr)) < 0) { log_error("Failed to bind server socket"); return -1; } if (listen(fd, MAX_PENDING) < 0) { log_error("Failed to listen on server socket"); return -1; } setnonblocking(fd); /* Add handler to handle events on fd */ return fd; } I used epoll for handling events, so adding a handler was something like this. ```c hash_set(ioloop->handlers, fd, handler); e.data.fd = fd; e.events = EPOLLIN; if(epoll_ctl(ioloop->epfd, EPOLL_CTL_ADD, fd, &e) < 0) { log_error("Failed to insert handler to epoll"); return -1; } Connections to the server should be accepted with accept() similar to TCP sockets. ## Connecting to the server ```c int connect_server() { struct sockaddr_un addr; int fd; if ((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { log_error("Failed to create client socket"); return fd; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strcpy(addr.sun_path, SOCKET_PATH); if (connect(fd, (struct sockaddr *) &(addr), sizeof(addr)) < 0) { log_error("Failed to connect to server"); return -1; } setnonblocking(fd); /* Add handler to handle events */ return fd; } I was adding ``EPOLLOUT`` listener to the socket whenever there were file descriptors to be passed to the other process. ## Receiving a file descriptor ```c static int recv_file_descriptor( int socket) /* Socket from which the file descriptor is read */ { int sent_fd; struct msghdr message; struct iovec iov[1]; struct cmsghdr *control_message = NULL; char ctrl_buf[CMSG_SPACE(sizeof(int))]; char data[1]; int res; memset(&message, 0, sizeof(struct msghdr)); memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); /* For the dummy data */ iov[0].iov_base = data; iov[0].iov_len = sizeof(data); message.msg_name = NULL; message.msg_namelen = 0; message.msg_control = ctrl_buf; message.msg_controllen = CMSG_SPACE(sizeof(int)); message.msg_iov = iov; message.msg_iovlen = 1; if((res = recvmsg(socket, &message, 0)) <= 0) return res; /* Iterate through header to find if there is a file descriptor */ for(control_message = CMSG_FIRSTHDR(&message); control_message != NULL; control_message = CMSG_NXTHDR(&message, control_message)) { if( (control_message->cmsg_level == SOL_SOCKET) && (control_message->cmsg_type == SCM_RIGHTS) ) { return *((int *) CMSG_DATA(control_message)); } } return -1; } ## Sending a file descriptor ```c static int send_file_descriptor( int socket, /* Socket through which the file descriptor is passed */ int fd_to_send) /* File descriptor to be passed, could be another socket */ { struct msghdr message; struct iovec iov[1]; struct cmsghdr *control_message = NULL; char ctrl_buf[CMSG_SPACE(sizeof(int))]; char data[1]; memset(&message, 0, sizeof(struct msghdr)); memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); /* We are passing at least one byte of data so that recvmsg() will not return 0 */ data[0] = ' '; iov[0].iov_base = data; iov[0].iov_len = sizeof(data); message.msg_name = NULL; message.msg_namelen = 0; message.msg_iov = iov; message.msg_iovlen = 1; message.msg_controllen = CMSG_SPACE(sizeof(int)); message.msg_control = ctrl_buf; control_message = CMSG_FIRSTHDR(&message); control_message->cmsg_level = SOL_SOCKET; control_message->cmsg_type = SCM_RIGHTS; control_message->cmsg_len = CMSG_LEN(sizeof(int)); *((int *) CMSG_DATA(control_message)) = fd_to_send; return sendmsg(socket, &message, 0); }