1 | /* Check recvmsg/recvmmsg 64-bit timestamp support. |
2 | Copyright (C) 2022-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <array_length.h> |
20 | #include <arpa/inet.h> |
21 | #include <errno.h> |
22 | #include <string.h> |
23 | #include <stdio.h> |
24 | #include <support/check.h> |
25 | #include <support/next_to_fault.h> |
26 | #include <support/support.h> |
27 | #include <support/test-driver.h> |
28 | #include <support/xunistd.h> |
29 | #include <support/xsocket.h> |
30 | #include <sys/mman.h> |
31 | |
32 | /* Some extra space added for ancillary data, it might be used to convert |
33 | 32-bit timestamp to 64-bit for _TIME_BITS=64. */ |
34 | enum { slack_max_size = 64 }; |
35 | static const int slack[] = { 0, 4, 8, 16, 32, slack_max_size }; |
36 | |
37 | static bool support_64_timestamp; |
38 | /* AF_INET socket and address used to receive data. */ |
39 | static int srv; |
40 | static struct sockaddr_in srv_addr; |
41 | |
42 | static int |
43 | do_sendto (const struct sockaddr_in *addr, int nmsgs) |
44 | { |
45 | int s = xsocket (AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); |
46 | xconnect (s, (const struct sockaddr *) addr, sizeof (*addr)); |
47 | |
48 | for (int i = 0; i < nmsgs; i++) |
49 | xsendto (s, &i, sizeof (i), 0, (const struct sockaddr *) addr, |
50 | sizeof (*addr)); |
51 | |
52 | xclose (s); |
53 | |
54 | return 0; |
55 | } |
56 | |
57 | static void |
58 | do_recvmsg_slack_ancillary (bool use_multi_call, int s, void *cmsg, |
59 | size_t slack, size_t tsize, int exp_payload) |
60 | { |
61 | int payload; |
62 | struct iovec iov = |
63 | { |
64 | .iov_base = &payload, |
65 | .iov_len = sizeof (payload) |
66 | }; |
67 | size_t msg_controllen = CMSG_SPACE (tsize) + slack; |
68 | char *msg_control = cmsg - msg_controllen; |
69 | memset (msg_control, 0x55, msg_controllen); |
70 | struct mmsghdr mmhdr = |
71 | { |
72 | .msg_hdr = |
73 | { |
74 | .msg_name = NULL, |
75 | .msg_namelen = 0, |
76 | .msg_iov = &iov, |
77 | .msg_iovlen = 1, |
78 | .msg_control = msg_control, |
79 | .msg_controllen = msg_controllen |
80 | }, |
81 | }; |
82 | |
83 | int r; |
84 | if (use_multi_call) |
85 | { |
86 | r = recvmmsg (fd: s, vmessages: &mmhdr, vlen: 1, flags: 0, NULL); |
87 | if (r >= 0) |
88 | r = mmhdr.msg_len; |
89 | } |
90 | else |
91 | r = recvmsg (fd: s, message: &mmhdr.msg_hdr, flags: 0); |
92 | TEST_COMPARE (r, sizeof (int)); |
93 | TEST_COMPARE (payload, exp_payload); |
94 | |
95 | if (cmsg == NULL) |
96 | return; |
97 | |
98 | /* A timestamp is expected if 32-bit timestamp are used (support in every |
99 | configuration) or if underlying kernel support 64-bit timestamps. |
100 | Otherwise recvmsg will need extra space do add the 64-bit timestamp. */ |
101 | bool exp_timestamp; |
102 | if (sizeof (time_t) == 4 || support_64_timestamp) |
103 | exp_timestamp = true; |
104 | else |
105 | exp_timestamp = slack >= CMSG_SPACE (tsize); |
106 | |
107 | bool timestamp = false; |
108 | for (struct cmsghdr *cmsg = CMSG_FIRSTHDR (&mmhdr.msg_hdr); |
109 | cmsg != NULL; |
110 | cmsg = CMSG_NXTHDR (&mmhdr.msg_hdr, cmsg)) |
111 | { |
112 | if (cmsg->cmsg_level != SOL_SOCKET) |
113 | continue; |
114 | if (cmsg->cmsg_type == SCM_TIMESTAMP |
115 | && cmsg->cmsg_len == CMSG_LEN (sizeof (struct timeval))) |
116 | { |
117 | struct timeval tv; |
118 | memcpy (&tv, CMSG_DATA (cmsg), sizeof (tv)); |
119 | if (test_verbose) |
120 | printf (format: "SCM_TIMESTAMP: {%jd, %jd}\n" , (intmax_t)tv.tv_sec, |
121 | (intmax_t)tv.tv_usec); |
122 | timestamp = true; |
123 | } |
124 | else if (cmsg->cmsg_type == SCM_TIMESTAMPNS |
125 | && cmsg->cmsg_len == CMSG_LEN (sizeof (struct timespec))) |
126 | { |
127 | struct timespec ts; |
128 | memcpy (&ts, CMSG_DATA (cmsg), sizeof (ts)); |
129 | if (test_verbose) |
130 | printf (format: "SCM_TIMESTAMPNS: {%jd, %jd}\n" , (intmax_t)ts.tv_sec, |
131 | (intmax_t)ts.tv_nsec); |
132 | timestamp = true; |
133 | } |
134 | } |
135 | |
136 | TEST_COMPARE (timestamp, exp_timestamp); |
137 | } |
138 | |
139 | /* Check if the extra ancillary space is correctly handled by recvmsg and |
140 | recvmmsg with different extra space for the ancillaty buffer. */ |
141 | static void |
142 | do_test_slack_space (void) |
143 | { |
144 | /* Setup the ancillary data buffer with an extra page with PROT_NONE to |
145 | check the possible timestamp conversion on some systems. */ |
146 | struct support_next_to_fault nf = |
147 | support_next_to_fault_allocate (size: slack_max_size); |
148 | void *msgbuf = nf.buffer + slack_max_size; |
149 | |
150 | /* Enable the timestamp using struct timeval precision. */ |
151 | { |
152 | int r = setsockopt (fd: srv, SOL_SOCKET, SO_TIMESTAMP, optval: &(int){1}, |
153 | optlen: sizeof (int)); |
154 | TEST_VERIFY_EXIT (r != -1); |
155 | } |
156 | /* Check recvmsg. */ |
157 | do_sendto (addr: &srv_addr, array_length (slack)); |
158 | for (int s = 0; s < array_length (slack); s++) |
159 | { |
160 | memset (nf.buffer, 0x55, nf.length); |
161 | do_recvmsg_slack_ancillary (false, s: srv, cmsg: msgbuf, slack: slack[s], |
162 | tsize: sizeof (struct timeval), exp_payload: s); |
163 | } |
164 | /* Check recvmmsg. */ |
165 | do_sendto (addr: &srv_addr, array_length (slack)); |
166 | for (int s = 0; s < array_length (slack); s++) |
167 | { |
168 | memset (nf.buffer, 0x55, nf.length); |
169 | do_recvmsg_slack_ancillary (true, s: srv, cmsg: msgbuf, slack: slack[s], |
170 | tsize: sizeof (struct timeval), exp_payload: s); |
171 | } |
172 | |
173 | /* Now enable timestamp using a higher precision, it overwrites the previous |
174 | precision. */ |
175 | { |
176 | int r = setsockopt (fd: srv, SOL_SOCKET, SO_TIMESTAMPNS, optval: &(int){1}, |
177 | optlen: sizeof (int)); |
178 | TEST_VERIFY_EXIT (r != -1); |
179 | } |
180 | /* Check recvmsg. */ |
181 | do_sendto (addr: &srv_addr, array_length (slack)); |
182 | for (int s = 0; s < array_length (slack); s++) |
183 | do_recvmsg_slack_ancillary (false, s: srv, cmsg: msgbuf, slack: slack[s], |
184 | tsize: sizeof (struct timespec), exp_payload: s); |
185 | /* Check recvmmsg. */ |
186 | do_sendto (addr: &srv_addr, array_length (slack)); |
187 | for (int s = 0; s < array_length (slack); s++) |
188 | do_recvmsg_slack_ancillary (true, s: srv, cmsg: msgbuf, slack: slack[s], |
189 | tsize: sizeof (struct timespec), exp_payload: s); |
190 | |
191 | support_next_to_fault_free (&nf); |
192 | } |
193 | |
194 | /* Check if the converted 64-bit timestamp is correctly appended when there |
195 | are multiple ancillary messages. */ |
196 | static void |
197 | do_recvmsg_multiple_ancillary (bool use_multi_call, int s, void *cmsg, |
198 | size_t cmsgsize, int exp_msg) |
199 | { |
200 | int msg; |
201 | struct iovec iov = |
202 | { |
203 | .iov_base = &msg, |
204 | .iov_len = sizeof (msg) |
205 | }; |
206 | size_t msgs = cmsgsize; |
207 | struct mmsghdr mmhdr = |
208 | { |
209 | .msg_hdr = |
210 | { |
211 | .msg_name = NULL, |
212 | .msg_namelen = 0, |
213 | .msg_iov = &iov, |
214 | .msg_iovlen = 1, |
215 | .msg_controllen = msgs, |
216 | .msg_control = cmsg, |
217 | }, |
218 | }; |
219 | |
220 | int r; |
221 | if (use_multi_call) |
222 | { |
223 | r = recvmmsg (fd: s, vmessages: &mmhdr, vlen: 1, flags: 0, NULL); |
224 | if (r >= 0) |
225 | r = mmhdr.msg_len; |
226 | } |
227 | else |
228 | r = recvmsg (fd: s, message: &mmhdr.msg_hdr, flags: 0); |
229 | TEST_COMPARE (r, sizeof (int)); |
230 | TEST_COMPARE (msg, exp_msg); |
231 | |
232 | if (cmsg == NULL) |
233 | return; |
234 | |
235 | bool timestamp = false; |
236 | bool origdstaddr = false; |
237 | for (struct cmsghdr *cmsg = CMSG_FIRSTHDR (&mmhdr.msg_hdr); |
238 | cmsg != NULL; |
239 | cmsg = CMSG_NXTHDR (&mmhdr.msg_hdr, cmsg)) |
240 | { |
241 | if (cmsg->cmsg_level == SOL_IP |
242 | && cmsg->cmsg_type == IP_ORIGDSTADDR |
243 | && cmsg->cmsg_len >= CMSG_LEN (sizeof (struct sockaddr_in))) |
244 | { |
245 | struct sockaddr_in sa; |
246 | memcpy (&sa, CMSG_DATA (cmsg), sizeof (sa)); |
247 | if (test_verbose) |
248 | { |
249 | char str[INET_ADDRSTRLEN]; |
250 | inet_ntop (AF_INET, &sa.sin_addr, str, INET_ADDRSTRLEN); |
251 | printf (format: "IP_ORIGDSTADDR: %s:%d\n" , str, ntohs (sa.sin_port)); |
252 | } |
253 | origdstaddr = sa.sin_addr.s_addr == srv_addr.sin_addr.s_addr |
254 | && sa.sin_port == srv_addr.sin_port; |
255 | } |
256 | if (cmsg->cmsg_level == SOL_SOCKET |
257 | && cmsg->cmsg_type == SCM_TIMESTAMP |
258 | && cmsg->cmsg_len >= CMSG_LEN (sizeof (struct timeval))) |
259 | { |
260 | struct timeval tv; |
261 | memcpy (&tv, CMSG_DATA (cmsg), sizeof (tv)); |
262 | if (test_verbose) |
263 | printf (format: "SCM_TIMESTAMP: {%jd, %jd}\n" , (intmax_t)tv.tv_sec, |
264 | (intmax_t)tv.tv_usec); |
265 | timestamp = true; |
266 | } |
267 | } |
268 | |
269 | TEST_COMPARE (timestamp, true); |
270 | TEST_COMPARE (origdstaddr, true); |
271 | } |
272 | |
273 | static void |
274 | do_test_multiple_ancillary (void) |
275 | { |
276 | { |
277 | int r = setsockopt (fd: srv, SOL_SOCKET, SO_TIMESTAMP, optval: &(int){1}, |
278 | optlen: sizeof (int)); |
279 | TEST_VERIFY_EXIT (r != -1); |
280 | } |
281 | { |
282 | int r = setsockopt (fd: srv, IPPROTO_IP, IP_RECVORIGDSTADDR, optval: &(int){1}, |
283 | optlen: sizeof (int)); |
284 | TEST_VERIFY_EXIT (r != -1); |
285 | } |
286 | |
287 | /* Enough data for default SO_TIMESTAMP, the IP_RECVORIGDSTADDR, and the |
288 | extra 64-bit SO_TIMESTAMP. */ |
289 | enum { msgbuflen = CMSG_SPACE (2 * sizeof (uint64_t)) |
290 | + CMSG_SPACE (sizeof (struct sockaddr_in)) |
291 | + CMSG_SPACE (2 * sizeof (uint64_t)) }; |
292 | char msgbuf[msgbuflen]; |
293 | |
294 | enum { nmsgs = 8 }; |
295 | /* Check recvmsg. */ |
296 | do_sendto (addr: &srv_addr, nmsgs); |
297 | for (int s = 0; s < nmsgs; s++) |
298 | do_recvmsg_multiple_ancillary (false, s: srv, cmsg: msgbuf, cmsgsize: msgbuflen, exp_msg: s); |
299 | /* Check recvmmsg. */ |
300 | do_sendto (addr: &srv_addr, nmsgs); |
301 | for (int s = 0; s < nmsgs; s++) |
302 | do_recvmsg_multiple_ancillary (true, s: srv, cmsg: msgbuf, cmsgsize: msgbuflen, exp_msg: s); |
303 | } |
304 | |
305 | static int |
306 | do_test (void) |
307 | { |
308 | srv = xsocket (AF_INET, SOCK_DGRAM, 0); |
309 | srv_addr = (struct sockaddr_in) { |
310 | .sin_family = AF_INET, |
311 | .sin_addr = {.s_addr = htonl (INADDR_LOOPBACK) }, |
312 | }; |
313 | xbind (srv, (struct sockaddr *) &srv_addr, sizeof (srv_addr)); |
314 | { |
315 | socklen_t sa_len = sizeof (srv_addr); |
316 | xgetsockname (srv, (struct sockaddr *) &srv_addr, &sa_len); |
317 | TEST_VERIFY (sa_len == sizeof (srv_addr)); |
318 | } |
319 | |
320 | TEST_COMPARE (recvmsg (-1, NULL, 0), -1); |
321 | TEST_COMPARE (errno, EBADF); |
322 | TEST_COMPARE (recvmmsg (-1, NULL, 0, 0, NULL), -1); |
323 | TEST_COMPARE (errno, EBADF); |
324 | |
325 | /* If underlying kernel does not support */ |
326 | support_64_timestamp = support_socket_so_timestamp_time64 (fd: srv); |
327 | |
328 | do_test_slack_space (); |
329 | do_test_multiple_ancillary (); |
330 | |
331 | xclose (srv); |
332 | |
333 | return 0; |
334 | } |
335 | |
336 | #include <support/test-driver.c> |
337 | |