FreeBSDのrecv(2)がこれから受け取るであろうデータのサイズを取得する
2018/11/22時点
OSSであるCRIUのコードを読んでいて、これからrecv(2)で取得するデータのサイズがわからないから、一度データサイズを取得してからバッファを確保するという処理に出会いました。
Linuxの場合
以下のコードはLinuxで実行すると受け取ろうとするデータのサイズを取得することができる。
int len; len = recv(fd, NULL, 0, MSG_TRUNC | MSG_PEEK); printf("size: %d\n", len);
MSG_TRUNC (since Linux 2.2) For raw (AF_PACKET), Internet datagram (since Linux 2.4.27/2.6.8), netlink (since Linux 2.6.22), and UNIX datagram (since Linux 3.4) sockets: return the real length of the packet or datagram, even when it was longer than the passed buffer.
本来であれば、recv(2)の返り値はバッファに格納したサイズになるのだが、 MSG_TRUNCを指定すれば、パケットやデータグラムの本当の長さを返すようにできる。
FreeBSDの場合
Linuxと同じコードを実行してもlen=0になる。 FreeBSD man recv(2)より
The msg_flags field is set on return according to the message received. MSG_EOR indicates end-of-record; the data returned completed a record (generally used with sockets of type SOCK_SEQPACKET). MSG_TRUNC indi- cates that the trailing portion of a datagram was discarded because the datagram was larger than the buffer supplied. MSG_CTRUNC indicates that some control data were discarded due to lack of space in the buffer for ancillary data. MSG_OOB is returned to indicate that expedited or out- of-band data were received.
MSG_TRUNCに関する部分を抜粋すると
MSG_TRUNC indicates that the trailing portion of a datagram was discarded because the datagram was larger than the buffer supplied.
バッファが足りない部分は破棄されたことを示すとあります。 よく文章を読んでみると
The msg_flags field is set on return according to the message received.
FreeBSDにおいては、MSG_TRUNCは受け取り専用のパラメータであり、recvのオプションとしてユーザランドから指定して使うことはできないようです。 一応バグとして報告されたこと(link)もありました。
また、FreeBSDのLinux互換レイヤLinuxulatorでもこのMSG_TRUNCは互換されている(link)ようですが、recv(2)のMSG_TRUNCは同じ挙動はしないことがわかったので、 Linux ELF on FreeBSDだとしても、MSG_TRUNCを使ってサイズを取得することはできないと思います。
もしも、受け取るデータのサイズを取得したい場合には、ioctl(2)のFIONREADを使うと思います。
ただ私が検証してみると、66バイトのデータを送信しているのにも関わらず、ioctlでサイズを取得すると82バイトとなってしまいます。 これがキュー内に新しいデータが入ってしまっていることが原因なのか、型の違いによるものなのか(GoとCの通信をしています)、これが正しい挙動なのか今はわかっていません。