9

when using recvmsg I use MSG_TRUNC and MSG_PEEK like so:

msgLen = recvmsg(fd, &hdr, MSG_PEEK | MSG_TRUNC)

this gives me the size of the buffer to allocate for the next message

my question is how do I get the size of the buffer I should allocate for the msg_control field inside the header

user3076936
  • 169
  • 13
  • This looks like it may be relevant: https://www.mirbsd.org/htman/i386/man3/CMSG_DATA.htm – Galik Feb 19 '18 at 09:07
  • @Galik those macros are for the messages in the buffer after you have already read them – user3076936 Feb 19 '18 at 09:23
  • In that case just read the data into the full buffer and then decompose it as Galik suggested. – o_weisman Feb 21 '18 at 12:31
  • @o_weisman my question is what size buffer should I allocate in order to receive all the messages. Galik gave a link to macros for handling the messages inside the buffer after receiving them from the kernel into the local buffer. – user3076936 Feb 21 '18 at 12:49
  • Isn't `msg_control` just filled by the callee? I mean, as far as the docs say, the `recvmsg` fills the header so I guess You don't need to bother with allocating storage for the `msg_control`. – bartop Feb 21 '18 at 15:42
  • @bartop it is filled by the callee but must be allocated by the caller, in fact it even returns a flag `MSG_CTRUNC` in case the `msg_control` buffer is too small – user3076936 Feb 22 '18 at 09:52

3 Answers3

3

Based on the doc, you need to allocate the buffer for msg_control of the size msg_controllen. To know the size beforehand, you could call like you did recvmsg(fd, &hdr, MSG_PEEK | MSG_TRUNC). MSG_PEEK won't remove the message and MSG_TRUNC will allow to return the size of the message, even if the buffer is too small.

a few solutions:

  • call recvmsg(fd, &hdr, MSG_PEEK | MSG_TRUNC) and init the buffer in hdr based on the size returned, and call it again without the flags.
  • allocate a buffer big enough, if you know the size of your messages beforehand, and call recvmsg. If an error occurs (returned -1), check the error code if the message was truncated (MSG_TRUNC or MSG_CTRUNC)
Syl
  • 2,733
  • 2
  • 17
  • 20
  • this will get me the size of the data not the size of `msg_controllen` I need in order to receive all ancillary data – user3076936 Feb 22 '18 at 09:49
  • that's why I said you can do a first call with MSG_PEEK to get the size of the data, and allocate the buffer accordingly. Or just allocate a buffer big enough. After a call of recvmsg, msg_controllen will be updated with the actual size of the data received. – Syl Feb 22 '18 at 12:13
  • the call to `recvmsg` does not update `msg_controllen` I've tries setting `msg_control` to `NULL` and `msg_controllen` to 0. neither changes on a call that I know to contain control data. – user3076936 Feb 22 '18 at 12:41
  • from the doc: `The field msg_control, which has length msg_controllen, points to a buffer for other protocol control-related messages or miscellaneous ancillary data. When recvmsg() is called, msg_controllen should con‐ tain the length of the available buffer in msg_control; upon return from a successful call it will contain the length of the control mes‐ sage sequence.` Maybe it didn't work because msg_control was NULL, or the type of message. – Syl Feb 22 '18 at 13:51
  • still It never returns the size of the data that would have been written. seems to me that the issue is that `recvmsg` accepts `MSG_TRUNC` as an argument in the `flags` field but not `MSG_CTRUNC` – user3076936 Feb 25 '18 at 07:13
2

I cannot speak for other platforms than macOS (whose core is based upon a FreeBSD core, so maybe it's no different in BSD-systems, too) and the POSIX standard is not helpful either as it leaves pretty much all details to be defined by the protocol, but by default behavior of recvmsg on macOS for a UDP socket is to not deliver any control data at all. No matter what size you set msg_control on input, it will always be 0 on output. If you wish to receive any control data, you first have to explicitly enable that for the socket.

E.g. if you want to know both addresses, source and destination address of a packet (msg_name only gives you the source address of a received packet), then you have to do this:

int yes = 1;
setsockopt(soc, IPPROTO_IP, IP_RECVDSTADDR, &yes, sizeof(yes));

And now you'll get the destination address for IPv4 sockets documented as

The msg_control field in the msghdr structure points to a buffer that contains a cmsghdr structure followed by the IP address. The cmsghdr fields have the following values:

cmsg_len = sizeof(struct in_addr)
cmsg_level = IPPROTO_IP
cmsg_type = IP_RECVDSTADDR

This means you need to provide at least 16 bytes storage on my system, as struct cmsghdr alone is always 12 bytes on that system (four times 32 bit) and an IPv4 address is another 4 bytes, that's 16 bytes together. This value needs to be correctly rounded using CMSG_SPACE macro, but on my system the macro only makes sure it's a multiple of 32 bit and 16 byte already is such a multiple, so CMSG_SPACE(16) returns 16 for me.

As I know in advance which options I have enabled and which control data I will receive, I can exactly calculate the required space in advance.

For raw and other more obscure sockets, certain control data may always be included in the output by default, even if not explicitly enabled, but this control data will then always be equal in size and won't fluctuate from packet to packet as the packet payload size does. Thus once you know the correct size, you can rely upon the fact that it won't change, at least not without you enabling/disabling any options.

If your control data buffer was too small, the MSG_CTRUNC flag is set in the output, always (even if you don't set any flags on input), then you need to increase the control data buffer size and try again (with the next packet or with the same packet if you used MSG_PEEK as input flag), until you've once been able to make that call without getting the MSG_CTRUNC flag on output. Finally look at what the msg_control field says. On input it's the amount of buffer space available but on output it contains the exact amount of buffer space that was actually used. This is the exact buffer size you need to receive the control data of all future packets of that socket, unless you change options that will cause more/less control data to be sent and then you just have to detect that size again the same way as before.

For a more complete example, you may also have a look at:
https://stackoverflow.com/a/49308499/15809

Mecki
  • 125,244
  • 33
  • 244
  • 253
1

I am afraid you can't get that value from the Posix.1g sockets API. Not sure about all implementations, but not possible in Linux. As you may notice, no control flow is provided in ancillary data buffers, so you will need to implement it yourself in case you are sending a lot of info between processes. On the other hand, for common case uses, you already know what you are going to receive at compile time (but you probably already know this). If you need to implement you own control flow, take into account that, in Linux, ancillary data seems to behave like a stream socket.

However, you can get/set the buffer length of the worst case scenario in /proc/sys/net/core/optmem_max, see cmsg(3). So, I guess you could set it to a reasonable value and declare a buffer that big.

Fusho
  • 1,469
  • 1
  • 10
  • 22