How to use sendmsg/recvmsg with cmsghdrs

Hi! I’m working on some IPC through a Unix local socket right now, and have ended up calling what’s defined in std.posix. The pattern I understand from C to build the msghdr for sendmsg(fd, *msghdr, flags) is with the CMSG_* macros from sys/socket.h:

Specifically in man cmsg I see:

The sequence of cmsghdr structures should never be accessed
directly. Instead, use only the following macros:

CMSG_FIRSTHDR()

CMSG_NXTHDR()

[…]

So I’m here to ask if there’s an alternative implementation for those macros, or if this part of the standard library is just not implemented yet and I should go ahead and manually traverse the buffer.

As a secondary thing, is this the first thing I should be trying when I want to open a stream socket to talk to a daemon, or is this a use case that’s actually handled by something in std.io?

Thanks for any input!

Hi @evaleek, the std library is largely filled with things that the compiler uses directly. It is not exhaustive, nor does it have direct translations for a lot of libc functions and macros. I looked through the standard library and couldn’t find anything that matched those macros. For something like this, you may have to like libc and add a addTranslateC step to translate that header file to zig. The translate C step should translate those macros into functions that you can call. As long as they don’t call any libc functions, then you may not have to link libc in your binary.

For your second question, I think it depends on what you are trying to do. There is a std.net.connectUnixSocket that will open a stream to talk to the daemon. I’m not sure what you need with the msghdr though, so that may not allow the customization you need.

Hi! Thanks again for looking into it. I actually just peeked into glibc, and it wasn’t too complicated (glibc/bits/socket.h), so here’s what I wrote for future reference:

pub const cmsg = struct {
    pub const hdr = extern struct {
        cmsg_len: system.socklen_t,
        cmsg_level: c_int,
        cmsg_type: c_int,
        // __extension__ unsigned char __cmsg_data __flexarr // Ancillary data.
    };

    // #define CMSG_DATA(cmsg) ((unsigned char *) (cmsg) + CMSG_ALIGN (sizeof (struct cmsghdr)))
    pub fn data(mhdr: *hdr) [*]u8 {
        return @as([*]u8, @ptrCast(mhdr)) + @"align"(@sizeOf(hdr));
    }

    // #define CMSG_FIRSTHDR(mhdr)
    // ((size_t) (mhdr)->msg_controllen >= sizeof (struct cmsghdr)
    //     ? (struct cmsghdr *) (mhdr)->msg_control
    //     : (struct cmsghdr *) 0)
    pub fn firsthdr(mhdr: posix.msghdr) ?*hdr {
        return if ( mhdr.msg_controllen >= @sizeOf(hdr) )
            @ptrCast(mhdr.msg_control) else null;
    }

    // #define CMSG_ALIGN(len) (((len) + sizeof (size_t) - 1) & (size_t) ~(sizeof (size_t) - 1))
    pub fn @"align"(length: usize) usize {
        const size_t_size: usize = @sizeOf(usize);
        const rem_bits: usize = size_t_size - 1;
        return ( length + rem_bits ) & ( ~rem_bits );
    }

    // #define CMSG_SPACE(len) (CMSG_ALIGN (len) + CMSG_ALIGN (sizeof (struct cmsghdr)))
    pub fn space(length: usize) usize {
        return @"align"(length) + @"align"(@sizeOf(hdr));
    }

    // #define CMSG_LEN(len) (CMSG_ALIGN (sizeof (struct cmsghdr)) + (len))
    pub fn len(length: usize) usize {
        return @"align"(@sizeOf(hdr)) + length;
    }
};

I’ll come back and make edits if I left an error in here. Also I haven’t done CMSG_NXTHDR yet because I don’t need it for what I’m doing right now.

Oh good thank you, I don’t know how I missed this. I think what I’m doing will need to play nice with 0.15 IO eventually, but it does seem like the CMSG stuff is necessary here because the daemon expects ancillary data.

I’m too stubborn for that :p. I would just try to get libpipewire-0.3 linked directly in that case I think

3 Likes

Just coming back to state everything more cleanly. Maybe too verbosely but I might need to come back and check this myself :slight_smile:

When sending data over a Unix domain socket (inter-process, on the same host) there is ancillary data that can be attached on the sendmsg() with the .control and .controllen fields of the msghdr. My specific use case here is transferring file descriptors to the receiver with SCM_RIGHTS: the control data is an array of file descriptor handles that the kernel will duplicate/transfer to the receiving process.

The pattern that C documentation gives, for portability reasons, is to always use the CMSG_* macros from libc’s sys/socket.h to populate a buffer with your control data. These basically just ensure correct placement and alignment of the control data, which ends up being (as far as I can tell on Linux specifically):

  • Control data is a sequence of the pair: a cmsghdr struct, the control data
  • These are each aligned to size_t/usize alignment

For this use case specifically, only one control message is needed. I:

  1. Have a static buffer of size CMSG_SPACE(bytes_per_fd * max_fds). CMSG_SPACE(x) is the byte size needed to fit a cmsghdr followed by x bytes of payload at the correct alignment. At each sendmsg:
  2. Initialize a cmsghdr at the start of the buffer, where .cmsg_level is SOL_SOCKET, .cmsg_type is SCM_RIGHTS (standard library was missing os.linux.SCM.RIGHTS), and .cmsg_len is CMSG_LEN(bytes_per_fd * fds.len). CMSG_LEN is defined as the macro you are supposed to use to initialize cmsghdr.cmsg_len, passing it the length of the control data for this header. It just adds the byte size to the aligned size of cmsghdr AKA gives you the length of the message pair with the proper alignment.
  3. Write the control data into the buffer. We are supposed to use CMSG_DATA, which first checks if the buffer can fit data, and then returns that aligned pointer to the starting point of the data for this header. The more Ziglike thing to do is reslice the buffer by [CMSG_ALIGN(@sizeOf(cmsghdr))..], then @memcpy in the fds. We will hit unreachable if the data doesn’t fit.
  4. Call sendmsg with cmsg.control pointing to the start of the buffer, and cmsg.controllen as the same CMSG_LEN(bytes_per_fd * fds.len) passed to cmsghdr.cmsg_len (the size of the control header + control data).

I assume C asks users to use the macros in case alignment requirements change on a different platform. I’m not sure if anything else varies.

I just had to hand-roll the buffer offsetting and struct building above, and use a magic number for SCM_RIGHTS because this constant is missing from the standard library. There’s probably a pretty way to initialize all of this with comptime.

1 Like