jklmnn
jklmnn

Reputation: 501

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?

Upvotes: 3

Views: 624

Answers (1)

jamieguinan
jamieguinan

Reputation: 1680

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.

Upvotes: 4

Related Questions