Search code examples
clinuxposix

How to determine size for multiple ancillary messages in linux control messages


I try to send multiple file descriptors over a unix socket at once. This is no problem for a single socket. Although when I try to append another one with CMSG_NXTHDR I get back a null pointer that indicates that my buffer was too short. For one file descriptor I calculated the buffer size with CMSG_SPACE and I assumed that I just need to multiply this. Although this doesn't seem to be sufficient. I wrote a short test program to check this:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>

int main(int argc, char *argv[])
{
    struct msghdr msg;

    if(argc != 2){
        return 1;
    }

    union {
        struct cmsghdr cmsghdr;
        char control[CMSG_SPACE(sizeof(int)) * atoi(argv[1])];
    } cmsgu;
    struct cmsghdr *cmsg;

    msg.msg_name = 0;
    msg.msg_namelen = 0;
    msg.msg_iov = 0;
    msg.msg_iovlen = 0;

    msg.msg_control = cmsgu.control;
    msg.msg_controllen = sizeof(cmsgu.control);

    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    *((int *)CMSG_DATA(cmsg)) = -1;

    cmsg = CMSG_NXTHDR(&msg, cmsg);
    fprintf(stderr, "%p\n", cmsg);
    return 0;
}

When I call this with 1 it outputs a null pointer which is expected. Altough my expectation would be that if it is called with 2 CMSG_NXTHDR would return a valid pointer. The first working value is 5 (so additional 120 bytes). I thought CMSG_SPACE would take care of this. Is there a way to go to calculate the required space? Or is there any more straight forward method to send multiple file descriptors in a single message?


Solution

  • See this question and answer. Your code should work if you memset() cmsgu to 0s.

    However (and to answer your second question), if you want to pass multiple file descriptors, you could use use a single cmsghdr containing an array of ints. Modifying your example,

        int n = atoi(argv[1]);
        int myfds[n]; // use this later
    
        union {
            struct cmsghdr cmsghdr; // for alignment
            char control[CMSG_SPACE(sizeof(int) * n)]; // space for one message
        } cmsgu;
        struct cmsghdr *cmsg;
    
        memset(&cmsgu, 0, sizeof(cmsgu)); // as noted
    
        msg.msg_name = 0;
        msg.msg_namelen = 0;
        msg.msg_iov = 0;
        msg.msg_iovlen = 0;
    
        msg.msg_control = cmsgu.control;
        msg.msg_controllen = sizeof(cmsgu.control);
    
        cmsg = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_len = CMSG_LEN(sizeof(int) * n);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
    
        int *fdptr = (int *) CMSG_DATA(cmsg);
        memcpy(fdptr, myfds, sizeof(int) * n);
    

    This is basically the same as the example in the cmsg(3) man page.