1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #define _GNU_SOURCE |
4 | |
5 | #include <assert.h> |
6 | #include <errno.h> |
7 | #include <fcntl.h> |
8 | #include <limits.h> |
9 | #include <string.h> |
10 | #include <stdarg.h> |
11 | #include <stdbool.h> |
12 | #include <stdint.h> |
13 | #include <inttypes.h> |
14 | #include <stdio.h> |
15 | #include <stdlib.h> |
16 | #include <strings.h> |
17 | #include <time.h> |
18 | #include <unistd.h> |
19 | |
20 | #include <sys/socket.h> |
21 | #include <sys/types.h> |
22 | #include <sys/wait.h> |
23 | |
24 | #include <netdb.h> |
25 | #include <netinet/in.h> |
26 | |
27 | #include <linux/tcp.h> |
28 | |
29 | static int pf = AF_INET; |
30 | |
31 | #ifndef IPPROTO_MPTCP |
32 | #define IPPROTO_MPTCP 262 |
33 | #endif |
34 | #ifndef SOL_MPTCP |
35 | #define SOL_MPTCP 284 |
36 | #endif |
37 | |
38 | #ifndef MPTCP_INFO |
39 | struct mptcp_info { |
40 | __u8 mptcpi_subflows; |
41 | __u8 mptcpi_add_addr_signal; |
42 | __u8 mptcpi_add_addr_accepted; |
43 | __u8 mptcpi_subflows_max; |
44 | __u8 mptcpi_add_addr_signal_max; |
45 | __u8 mptcpi_add_addr_accepted_max; |
46 | __u32 mptcpi_flags; |
47 | __u32 mptcpi_token; |
48 | __u64 mptcpi_write_seq; |
49 | __u64 mptcpi_snd_una; |
50 | __u64 mptcpi_rcv_nxt; |
51 | __u8 mptcpi_local_addr_used; |
52 | __u8 mptcpi_local_addr_max; |
53 | __u8 mptcpi_csum_enabled; |
54 | __u32 mptcpi_retransmits; |
55 | __u64 mptcpi_bytes_retrans; |
56 | __u64 mptcpi_bytes_sent; |
57 | __u64 mptcpi_bytes_received; |
58 | __u64 mptcpi_bytes_acked; |
59 | }; |
60 | |
61 | struct mptcp_subflow_data { |
62 | __u32 size_subflow_data; /* size of this structure in userspace */ |
63 | __u32 num_subflows; /* must be 0, set by kernel */ |
64 | __u32 size_kernel; /* must be 0, set by kernel */ |
65 | __u32 size_user; /* size of one element in data[] */ |
66 | } __attribute__((aligned(8))); |
67 | |
68 | struct mptcp_subflow_addrs { |
69 | union { |
70 | __kernel_sa_family_t sa_family; |
71 | struct sockaddr sa_local; |
72 | struct sockaddr_in sin_local; |
73 | struct sockaddr_in6 sin6_local; |
74 | struct __kernel_sockaddr_storage ss_local; |
75 | }; |
76 | union { |
77 | struct sockaddr sa_remote; |
78 | struct sockaddr_in sin_remote; |
79 | struct sockaddr_in6 sin6_remote; |
80 | struct __kernel_sockaddr_storage ss_remote; |
81 | }; |
82 | }; |
83 | |
84 | #define MPTCP_INFO 1 |
85 | #define MPTCP_TCPINFO 2 |
86 | #define MPTCP_SUBFLOW_ADDRS 3 |
87 | #endif |
88 | |
89 | #ifndef MPTCP_FULL_INFO |
90 | struct mptcp_subflow_info { |
91 | __u32 id; |
92 | struct mptcp_subflow_addrs addrs; |
93 | }; |
94 | |
95 | struct mptcp_full_info { |
96 | __u32 size_tcpinfo_kernel; /* must be 0, set by kernel */ |
97 | __u32 size_tcpinfo_user; |
98 | __u32 size_sfinfo_kernel; /* must be 0, set by kernel */ |
99 | __u32 size_sfinfo_user; |
100 | __u32 num_subflows; /* must be 0, set by kernel (real subflow count) */ |
101 | __u32 size_arrays_user; /* max subflows that userspace is interested in; |
102 | * the buffers at subflow_info/tcp_info |
103 | * are respectively at least: |
104 | * size_arrays * size_sfinfo_user |
105 | * size_arrays * size_tcpinfo_user |
106 | * bytes wide |
107 | */ |
108 | __aligned_u64 subflow_info; |
109 | __aligned_u64 tcp_info; |
110 | struct mptcp_info mptcp_info; |
111 | }; |
112 | |
113 | #define MPTCP_FULL_INFO 4 |
114 | #endif |
115 | |
116 | struct so_state { |
117 | struct mptcp_info mi; |
118 | struct mptcp_info last_sample; |
119 | struct tcp_info tcp_info; |
120 | struct mptcp_subflow_addrs addrs; |
121 | uint64_t mptcpi_rcv_delta; |
122 | uint64_t tcpi_rcv_delta; |
123 | bool pkt_stats_avail; |
124 | }; |
125 | |
126 | #ifndef MIN |
127 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) |
128 | #endif |
129 | |
130 | static void die_perror(const char *msg) |
131 | { |
132 | perror(msg); |
133 | exit(1); |
134 | } |
135 | |
136 | static void die_usage(int r) |
137 | { |
138 | fprintf(stderr, "Usage: mptcp_sockopt [-6]\n" ); |
139 | exit(r); |
140 | } |
141 | |
142 | static void xerror(const char *fmt, ...) |
143 | { |
144 | va_list ap; |
145 | |
146 | va_start(ap, fmt); |
147 | vfprintf(stderr, fmt, ap); |
148 | va_end(ap); |
149 | fputc('\n', stderr); |
150 | exit(1); |
151 | } |
152 | |
153 | static const char *getxinfo_strerr(int err) |
154 | { |
155 | if (err == EAI_SYSTEM) |
156 | return strerror(errno); |
157 | |
158 | return gai_strerror(err); |
159 | } |
160 | |
161 | static void xgetaddrinfo(const char *node, const char *service, |
162 | const struct addrinfo *hints, |
163 | struct addrinfo **res) |
164 | { |
165 | int err = getaddrinfo(node, service, hints, res); |
166 | |
167 | if (err) { |
168 | const char *errstr = getxinfo_strerr(err); |
169 | |
170 | fprintf(stderr, "Fatal: getaddrinfo(%s:%s): %s\n" , |
171 | node ? node : "" , service ? service : "" , errstr); |
172 | exit(1); |
173 | } |
174 | } |
175 | |
176 | static int sock_listen_mptcp(const char * const listenaddr, |
177 | const char * const port) |
178 | { |
179 | int sock = -1; |
180 | struct addrinfo hints = { |
181 | .ai_protocol = IPPROTO_TCP, |
182 | .ai_socktype = SOCK_STREAM, |
183 | .ai_flags = AI_PASSIVE | AI_NUMERICHOST |
184 | }; |
185 | |
186 | hints.ai_family = pf; |
187 | |
188 | struct addrinfo *a, *addr; |
189 | int one = 1; |
190 | |
191 | xgetaddrinfo(node: listenaddr, service: port, hints: &hints, res: &addr); |
192 | hints.ai_family = pf; |
193 | |
194 | for (a = addr; a; a = a->ai_next) { |
195 | sock = socket(a->ai_family, a->ai_socktype, IPPROTO_MPTCP); |
196 | if (sock < 0) |
197 | continue; |
198 | |
199 | if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, |
200 | sizeof(one))) |
201 | perror("setsockopt" ); |
202 | |
203 | if (bind(sock, a->ai_addr, a->ai_addrlen) == 0) |
204 | break; /* success */ |
205 | |
206 | perror("bind" ); |
207 | close(sock); |
208 | sock = -1; |
209 | } |
210 | |
211 | freeaddrinfo(addr); |
212 | |
213 | if (sock < 0) |
214 | xerror(fmt: "could not create listen socket" ); |
215 | |
216 | if (listen(sock, 20)) |
217 | die_perror(msg: "listen" ); |
218 | |
219 | return sock; |
220 | } |
221 | |
222 | static int sock_connect_mptcp(const char * const remoteaddr, |
223 | const char * const port, int proto) |
224 | { |
225 | struct addrinfo hints = { |
226 | .ai_protocol = IPPROTO_TCP, |
227 | .ai_socktype = SOCK_STREAM, |
228 | }; |
229 | struct addrinfo *a, *addr; |
230 | int sock = -1; |
231 | |
232 | hints.ai_family = pf; |
233 | |
234 | xgetaddrinfo(node: remoteaddr, service: port, hints: &hints, res: &addr); |
235 | for (a = addr; a; a = a->ai_next) { |
236 | sock = socket(a->ai_family, a->ai_socktype, proto); |
237 | if (sock < 0) |
238 | continue; |
239 | |
240 | if (connect(sock, a->ai_addr, a->ai_addrlen) == 0) |
241 | break; /* success */ |
242 | |
243 | die_perror(msg: "connect" ); |
244 | } |
245 | |
246 | if (sock < 0) |
247 | xerror(fmt: "could not create connect socket" ); |
248 | |
249 | freeaddrinfo(addr); |
250 | return sock; |
251 | } |
252 | |
253 | static void parse_opts(int argc, char **argv) |
254 | { |
255 | int c; |
256 | |
257 | while ((c = getopt(argc, argv, "h6" )) != -1) { |
258 | switch (c) { |
259 | case 'h': |
260 | die_usage(r: 0); |
261 | break; |
262 | case '6': |
263 | pf = AF_INET6; |
264 | break; |
265 | default: |
266 | die_usage(r: 1); |
267 | break; |
268 | } |
269 | } |
270 | } |
271 | |
272 | static void do_getsockopt_bogus_sf_data(int fd, int optname) |
273 | { |
274 | struct mptcp_subflow_data good_data; |
275 | struct bogus_data { |
276 | struct mptcp_subflow_data d; |
277 | char buf[2]; |
278 | } bd; |
279 | socklen_t olen, _olen; |
280 | int ret; |
281 | |
282 | memset(&bd, 0, sizeof(bd)); |
283 | memset(&good_data, 0, sizeof(good_data)); |
284 | |
285 | olen = sizeof(good_data); |
286 | good_data.size_subflow_data = olen; |
287 | |
288 | ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen); |
289 | assert(ret < 0); /* 0 size_subflow_data */ |
290 | assert(olen == sizeof(good_data)); |
291 | |
292 | bd.d = good_data; |
293 | |
294 | ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen); |
295 | assert(ret == 0); |
296 | assert(olen == sizeof(good_data)); |
297 | assert(bd.d.num_subflows == 1); |
298 | assert(bd.d.size_kernel > 0); |
299 | assert(bd.d.size_user == 0); |
300 | |
301 | bd.d = good_data; |
302 | _olen = rand() % olen; |
303 | olen = _olen; |
304 | ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen); |
305 | assert(ret < 0); /* bogus olen */ |
306 | assert(olen == _olen); /* must be unchanged */ |
307 | |
308 | bd.d = good_data; |
309 | olen = sizeof(good_data); |
310 | bd.d.size_kernel = 1; |
311 | ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen); |
312 | assert(ret < 0); /* size_kernel not 0 */ |
313 | |
314 | bd.d = good_data; |
315 | olen = sizeof(good_data); |
316 | bd.d.num_subflows = 1; |
317 | ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen); |
318 | assert(ret < 0); /* num_subflows not 0 */ |
319 | |
320 | /* forward compat check: larger struct mptcp_subflow_data on 'old' kernel */ |
321 | bd.d = good_data; |
322 | olen = sizeof(bd); |
323 | bd.d.size_subflow_data = sizeof(bd); |
324 | |
325 | ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen); |
326 | assert(ret == 0); |
327 | |
328 | /* olen must be truncated to real data size filled by kernel: */ |
329 | assert(olen == sizeof(good_data)); |
330 | |
331 | assert(bd.d.size_subflow_data == sizeof(bd)); |
332 | |
333 | bd.d = good_data; |
334 | bd.d.size_subflow_data += 1; |
335 | bd.d.size_user = 1; |
336 | olen = bd.d.size_subflow_data + 1; |
337 | _olen = olen; |
338 | |
339 | ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &_olen); |
340 | assert(ret == 0); |
341 | |
342 | /* no truncation, kernel should have filled 1 byte of optname payload in buf[1]: */ |
343 | assert(olen == _olen); |
344 | |
345 | assert(bd.d.size_subflow_data == sizeof(good_data) + 1); |
346 | assert(bd.buf[0] == 0); |
347 | } |
348 | |
349 | static void do_getsockopt_mptcp_info(struct so_state *s, int fd, size_t w) |
350 | { |
351 | struct mptcp_info i; |
352 | socklen_t olen; |
353 | int ret; |
354 | |
355 | olen = sizeof(i); |
356 | ret = getsockopt(fd, SOL_MPTCP, MPTCP_INFO, &i, &olen); |
357 | |
358 | if (ret < 0) |
359 | die_perror(msg: "getsockopt MPTCP_INFO" ); |
360 | |
361 | s->pkt_stats_avail = olen >= sizeof(i); |
362 | |
363 | s->last_sample = i; |
364 | if (s->mi.mptcpi_write_seq == 0) |
365 | s->mi = i; |
366 | |
367 | assert(s->mi.mptcpi_write_seq + w == i.mptcpi_write_seq); |
368 | |
369 | s->mptcpi_rcv_delta = i.mptcpi_rcv_nxt - s->mi.mptcpi_rcv_nxt; |
370 | } |
371 | |
372 | static void do_getsockopt_tcp_info(struct so_state *s, int fd, size_t r, size_t w) |
373 | { |
374 | struct my_tcp_info { |
375 | struct mptcp_subflow_data d; |
376 | struct tcp_info ti[2]; |
377 | } ti; |
378 | int ret, tries = 5; |
379 | socklen_t olen; |
380 | |
381 | do { |
382 | memset(&ti, 0, sizeof(ti)); |
383 | |
384 | ti.d.size_subflow_data = sizeof(struct mptcp_subflow_data); |
385 | ti.d.size_user = sizeof(struct tcp_info); |
386 | olen = sizeof(ti); |
387 | |
388 | ret = getsockopt(fd, SOL_MPTCP, MPTCP_TCPINFO, &ti, &olen); |
389 | if (ret < 0) |
390 | xerror(fmt: "getsockopt MPTCP_TCPINFO (tries %d, %m)" ); |
391 | |
392 | assert(olen <= sizeof(ti)); |
393 | assert(ti.d.size_kernel > 0); |
394 | assert(ti.d.size_user == |
395 | MIN(ti.d.size_kernel, sizeof(struct tcp_info))); |
396 | assert(ti.d.num_subflows == 1); |
397 | |
398 | assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data)); |
399 | olen -= sizeof(struct mptcp_subflow_data); |
400 | assert(olen == ti.d.size_user); |
401 | |
402 | s->tcp_info = ti.ti[0]; |
403 | |
404 | if (ti.ti[0].tcpi_bytes_sent == w && |
405 | ti.ti[0].tcpi_bytes_received == r) |
406 | goto done; |
407 | |
408 | if (r == 0 && ti.ti[0].tcpi_bytes_sent == w && |
409 | ti.ti[0].tcpi_bytes_received) { |
410 | s->tcpi_rcv_delta = ti.ti[0].tcpi_bytes_received; |
411 | goto done; |
412 | } |
413 | |
414 | /* wait and repeat, might be that tx is still ongoing */ |
415 | sleep(1); |
416 | } while (tries-- > 0); |
417 | |
418 | xerror("tcpi_bytes_sent %" PRIu64 ", want %zu. tcpi_bytes_received %" PRIu64 ", want %zu" , |
419 | ti.ti[0].tcpi_bytes_sent, w, ti.ti[0].tcpi_bytes_received, r); |
420 | |
421 | done: |
422 | do_getsockopt_bogus_sf_data(fd, MPTCP_TCPINFO); |
423 | } |
424 | |
425 | static void do_getsockopt_subflow_addrs(struct so_state *s, int fd) |
426 | { |
427 | struct sockaddr_storage remote, local; |
428 | socklen_t olen, rlen, llen; |
429 | int ret; |
430 | struct my_addrs { |
431 | struct mptcp_subflow_data d; |
432 | struct mptcp_subflow_addrs addr[2]; |
433 | } addrs; |
434 | |
435 | memset(&addrs, 0, sizeof(addrs)); |
436 | memset(&local, 0, sizeof(local)); |
437 | memset(&remote, 0, sizeof(remote)); |
438 | |
439 | addrs.d.size_subflow_data = sizeof(struct mptcp_subflow_data); |
440 | addrs.d.size_user = sizeof(struct mptcp_subflow_addrs); |
441 | olen = sizeof(addrs); |
442 | |
443 | ret = getsockopt(fd, SOL_MPTCP, MPTCP_SUBFLOW_ADDRS, &addrs, &olen); |
444 | if (ret < 0) |
445 | die_perror(msg: "getsockopt MPTCP_SUBFLOW_ADDRS" ); |
446 | |
447 | assert(olen <= sizeof(addrs)); |
448 | assert(addrs.d.size_kernel > 0); |
449 | assert(addrs.d.size_user == |
450 | MIN(addrs.d.size_kernel, sizeof(struct mptcp_subflow_addrs))); |
451 | assert(addrs.d.num_subflows == 1); |
452 | |
453 | assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data)); |
454 | olen -= sizeof(struct mptcp_subflow_data); |
455 | assert(olen == addrs.d.size_user); |
456 | |
457 | llen = sizeof(local); |
458 | ret = getsockname(fd, (struct sockaddr *)&local, &llen); |
459 | if (ret < 0) |
460 | die_perror(msg: "getsockname" ); |
461 | rlen = sizeof(remote); |
462 | ret = getpeername(fd, (struct sockaddr *)&remote, &rlen); |
463 | if (ret < 0) |
464 | die_perror(msg: "getpeername" ); |
465 | |
466 | assert(rlen > 0); |
467 | assert(rlen == llen); |
468 | |
469 | assert(remote.ss_family == local.ss_family); |
470 | |
471 | assert(memcmp(p: &local, q: &addrs.addr[0].ss_local, size: sizeof(local)) == 0); |
472 | assert(memcmp(p: &remote, q: &addrs.addr[0].ss_remote, size: sizeof(remote)) == 0); |
473 | s->addrs = addrs.addr[0]; |
474 | |
475 | memset(&addrs, 0, sizeof(addrs)); |
476 | |
477 | addrs.d.size_subflow_data = sizeof(struct mptcp_subflow_data); |
478 | addrs.d.size_user = sizeof(sa_family_t); |
479 | olen = sizeof(addrs.d) + sizeof(sa_family_t); |
480 | |
481 | ret = getsockopt(fd, SOL_MPTCP, MPTCP_SUBFLOW_ADDRS, &addrs, &olen); |
482 | assert(ret == 0); |
483 | assert(olen == sizeof(addrs.d) + sizeof(sa_family_t)); |
484 | |
485 | assert(addrs.addr[0].sa_family == pf); |
486 | assert(addrs.addr[0].sa_family == local.ss_family); |
487 | |
488 | assert(memcmp(p: &local, q: &addrs.addr[0].ss_local, size: sizeof(local)) != 0); |
489 | assert(memcmp(p: &remote, q: &addrs.addr[0].ss_remote, size: sizeof(remote)) != 0); |
490 | |
491 | do_getsockopt_bogus_sf_data(fd, MPTCP_SUBFLOW_ADDRS); |
492 | } |
493 | |
494 | static void do_getsockopt_mptcp_full_info(struct so_state *s, int fd) |
495 | { |
496 | size_t data_size = sizeof(struct mptcp_full_info); |
497 | struct mptcp_subflow_info sfinfo[2]; |
498 | struct tcp_info tcp_info[2]; |
499 | struct mptcp_full_info mfi; |
500 | socklen_t olen; |
501 | int ret; |
502 | |
503 | memset(&mfi, 0, data_size); |
504 | memset(tcp_info, 0, sizeof(tcp_info)); |
505 | memset(sfinfo, 0, sizeof(sfinfo)); |
506 | |
507 | mfi.size_tcpinfo_user = sizeof(struct tcp_info); |
508 | mfi.size_sfinfo_user = sizeof(struct mptcp_subflow_info); |
509 | mfi.size_arrays_user = 2; |
510 | mfi.subflow_info = (unsigned long)&sfinfo[0]; |
511 | mfi.tcp_info = (unsigned long)&tcp_info[0]; |
512 | olen = data_size; |
513 | |
514 | ret = getsockopt(fd, SOL_MPTCP, MPTCP_FULL_INFO, &mfi, &olen); |
515 | if (ret < 0) { |
516 | if (errno == EOPNOTSUPP) { |
517 | perror("MPTCP_FULL_INFO test skipped" ); |
518 | return; |
519 | } |
520 | xerror(fmt: "getsockopt MPTCP_FULL_INFO" ); |
521 | } |
522 | |
523 | assert(olen <= data_size); |
524 | assert(mfi.size_tcpinfo_kernel > 0); |
525 | assert(mfi.size_tcpinfo_user == |
526 | MIN(mfi.size_tcpinfo_kernel, sizeof(struct tcp_info))); |
527 | assert(mfi.size_sfinfo_kernel > 0); |
528 | assert(mfi.size_sfinfo_user == |
529 | MIN(mfi.size_sfinfo_kernel, sizeof(struct mptcp_subflow_info))); |
530 | assert(mfi.num_subflows == 1); |
531 | |
532 | /* Tolerate future extension to mptcp_info struct and running newer |
533 | * test on top of older kernel. |
534 | * Anyway any kernel supporting MPTCP_FULL_INFO must at least include |
535 | * the following in mptcp_info. |
536 | */ |
537 | assert(olen > (socklen_t)__builtin_offsetof(struct mptcp_full_info, tcp_info)); |
538 | assert(mfi.mptcp_info.mptcpi_subflows == 0); |
539 | assert(mfi.mptcp_info.mptcpi_bytes_sent == s->last_sample.mptcpi_bytes_sent); |
540 | assert(mfi.mptcp_info.mptcpi_bytes_received == s->last_sample.mptcpi_bytes_received); |
541 | |
542 | assert(sfinfo[0].id == 1); |
543 | assert(tcp_info[0].tcpi_bytes_sent == s->tcp_info.tcpi_bytes_sent); |
544 | assert(tcp_info[0].tcpi_bytes_received == s->tcp_info.tcpi_bytes_received); |
545 | assert(!memcmp(p: &sfinfo->addrs, q: &s->addrs, size: sizeof(struct mptcp_subflow_addrs))); |
546 | } |
547 | |
548 | static void do_getsockopts(struct so_state *s, int fd, size_t r, size_t w) |
549 | { |
550 | do_getsockopt_mptcp_info(s, fd, w); |
551 | |
552 | do_getsockopt_tcp_info(s, fd, r, w); |
553 | |
554 | do_getsockopt_subflow_addrs(s, fd); |
555 | |
556 | if (r) |
557 | do_getsockopt_mptcp_full_info(s, fd); |
558 | } |
559 | |
560 | static void connect_one_server(int fd, int pipefd) |
561 | { |
562 | char buf[4096], buf2[4096]; |
563 | size_t len, i, total; |
564 | struct so_state s; |
565 | bool eof = false; |
566 | ssize_t ret; |
567 | |
568 | memset(&s, 0, sizeof(s)); |
569 | |
570 | len = rand() % (sizeof(buf) - 1); |
571 | |
572 | if (len < 128) |
573 | len = 128; |
574 | |
575 | for (i = 0; i < len ; i++) { |
576 | buf[i] = rand() % 26; |
577 | buf[i] += 'A'; |
578 | } |
579 | |
580 | buf[i] = '\n'; |
581 | |
582 | do_getsockopts(s: &s, fd, r: 0, w: 0); |
583 | |
584 | /* un-block server */ |
585 | ret = read(pipefd, buf2, 4); |
586 | assert(ret == 4); |
587 | close(pipefd); |
588 | |
589 | assert(strncmp(buf2, "xmit" , 4) == 0); |
590 | |
591 | ret = write(fd, buf, len); |
592 | if (ret < 0) |
593 | die_perror(msg: "write" ); |
594 | |
595 | if (ret != (ssize_t)len) |
596 | xerror(fmt: "short write" ); |
597 | |
598 | total = 0; |
599 | do { |
600 | ret = read(fd, buf2 + total, sizeof(buf2) - total); |
601 | if (ret < 0) |
602 | die_perror(msg: "read" ); |
603 | if (ret == 0) { |
604 | eof = true; |
605 | break; |
606 | } |
607 | |
608 | total += ret; |
609 | } while (total < len); |
610 | |
611 | if (total != len) |
612 | xerror(fmt: "total %lu, len %lu eof %d\n" , total, len, eof); |
613 | |
614 | if (memcmp(p: buf, q: buf2, size: len)) |
615 | xerror(fmt: "data corruption" ); |
616 | |
617 | if (s.tcpi_rcv_delta) |
618 | assert(s.tcpi_rcv_delta <= total); |
619 | |
620 | do_getsockopts(s: &s, fd, r: ret, w: ret); |
621 | |
622 | if (eof) |
623 | total += 1; /* sequence advances due to FIN */ |
624 | |
625 | assert(s.mptcpi_rcv_delta == (uint64_t)total); |
626 | close(fd); |
627 | } |
628 | |
629 | static void process_one_client(int fd, int pipefd) |
630 | { |
631 | ssize_t ret, ret2, ret3; |
632 | struct so_state s; |
633 | char buf[4096]; |
634 | |
635 | memset(&s, 0, sizeof(s)); |
636 | do_getsockopts(s: &s, fd, r: 0, w: 0); |
637 | |
638 | ret = write(pipefd, "xmit" , 4); |
639 | assert(ret == 4); |
640 | |
641 | ret = read(fd, buf, sizeof(buf)); |
642 | if (ret < 0) |
643 | die_perror(msg: "read" ); |
644 | |
645 | assert(s.mptcpi_rcv_delta <= (uint64_t)ret); |
646 | |
647 | if (s.tcpi_rcv_delta) |
648 | assert(s.tcpi_rcv_delta == (uint64_t)ret); |
649 | |
650 | ret2 = write(fd, buf, ret); |
651 | if (ret2 < 0) |
652 | die_perror(msg: "write" ); |
653 | |
654 | /* wait for hangup */ |
655 | ret3 = read(fd, buf, 1); |
656 | if (ret3 != 0) |
657 | xerror(fmt: "expected EOF, got %lu" , ret3); |
658 | |
659 | do_getsockopts(s: &s, fd, r: ret, w: ret2); |
660 | if (s.mptcpi_rcv_delta != (uint64_t)ret + 1) |
661 | xerror("mptcpi_rcv_delta %" PRIu64 ", expect %" PRIu64, s.mptcpi_rcv_delta, ret + 1, s.mptcpi_rcv_delta - ret); |
662 | |
663 | /* be nice when running on top of older kernel */ |
664 | if (s.pkt_stats_avail) { |
665 | if (s.last_sample.mptcpi_bytes_sent != ret2) |
666 | xerror("mptcpi_bytes_sent %" PRIu64 ", expect %" PRIu64, |
667 | s.last_sample.mptcpi_bytes_sent, ret2, |
668 | s.last_sample.mptcpi_bytes_sent - ret2); |
669 | if (s.last_sample.mptcpi_bytes_received != ret) |
670 | xerror("mptcpi_bytes_received %" PRIu64 ", expect %" PRIu64, |
671 | s.last_sample.mptcpi_bytes_received, ret, |
672 | s.last_sample.mptcpi_bytes_received - ret); |
673 | if (s.last_sample.mptcpi_bytes_acked != ret) |
674 | xerror("mptcpi_bytes_acked %" PRIu64 ", expect %" PRIu64, |
675 | s.last_sample.mptcpi_bytes_acked, ret2, |
676 | s.last_sample.mptcpi_bytes_acked - ret2); |
677 | } |
678 | |
679 | close(fd); |
680 | } |
681 | |
682 | static int xaccept(int s) |
683 | { |
684 | int fd = accept(s, NULL, 0); |
685 | |
686 | if (fd < 0) |
687 | die_perror(msg: "accept" ); |
688 | |
689 | return fd; |
690 | } |
691 | |
692 | static int server(int pipefd) |
693 | { |
694 | int fd = -1, r; |
695 | |
696 | switch (pf) { |
697 | case AF_INET: |
698 | fd = sock_listen_mptcp(listenaddr: "127.0.0.1" , port: "15432" ); |
699 | break; |
700 | case AF_INET6: |
701 | fd = sock_listen_mptcp(listenaddr: "::1" , port: "15432" ); |
702 | break; |
703 | default: |
704 | xerror(fmt: "Unknown pf %d\n" , pf); |
705 | break; |
706 | } |
707 | |
708 | r = write(pipefd, "conn" , 4); |
709 | assert(r == 4); |
710 | |
711 | alarm(15); |
712 | r = xaccept(s: fd); |
713 | |
714 | process_one_client(fd: r, pipefd); |
715 | |
716 | return 0; |
717 | } |
718 | |
719 | static void test_ip_tos_sockopt(int fd) |
720 | { |
721 | uint8_t tos_in, tos_out; |
722 | socklen_t s; |
723 | int r; |
724 | |
725 | tos_in = rand() & 0xfc; |
726 | r = setsockopt(fd, SOL_IP, IP_TOS, &tos_in, sizeof(tos_out)); |
727 | if (r != 0) |
728 | die_perror(msg: "setsockopt IP_TOS" ); |
729 | |
730 | tos_out = 0; |
731 | s = sizeof(tos_out); |
732 | r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s); |
733 | if (r != 0) |
734 | die_perror(msg: "getsockopt IP_TOS" ); |
735 | |
736 | if (tos_in != tos_out) |
737 | xerror("tos %x != %x socklen_t %d\n" , tos_in, tos_out, s); |
738 | |
739 | if (s != 1) |
740 | xerror(fmt: "tos should be 1 byte" ); |
741 | |
742 | s = 0; |
743 | r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s); |
744 | if (r != 0) |
745 | die_perror(msg: "getsockopt IP_TOS 0" ); |
746 | if (s != 0) |
747 | xerror(fmt: "expect socklen_t == 0" ); |
748 | |
749 | s = -1; |
750 | r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s); |
751 | if (r != -1 && errno != EINVAL) |
752 | die_perror(msg: "getsockopt IP_TOS did not indicate -EINVAL" ); |
753 | if (s != -1) |
754 | xerror(fmt: "expect socklen_t == -1" ); |
755 | } |
756 | |
757 | static int client(int pipefd) |
758 | { |
759 | int fd = -1; |
760 | |
761 | alarm(15); |
762 | |
763 | switch (pf) { |
764 | case AF_INET: |
765 | fd = sock_connect_mptcp(remoteaddr: "127.0.0.1" , port: "15432" , IPPROTO_MPTCP); |
766 | break; |
767 | case AF_INET6: |
768 | fd = sock_connect_mptcp(remoteaddr: "::1" , port: "15432" , IPPROTO_MPTCP); |
769 | break; |
770 | default: |
771 | xerror(fmt: "Unknown pf %d\n" , pf); |
772 | } |
773 | |
774 | test_ip_tos_sockopt(fd); |
775 | |
776 | connect_one_server(fd, pipefd); |
777 | |
778 | return 0; |
779 | } |
780 | |
781 | static pid_t xfork(void) |
782 | { |
783 | pid_t p = fork(); |
784 | |
785 | if (p < 0) |
786 | die_perror(msg: "fork" ); |
787 | |
788 | return p; |
789 | } |
790 | |
791 | static int rcheck(int wstatus, const char *what) |
792 | { |
793 | if (WIFEXITED(wstatus)) { |
794 | if (WEXITSTATUS(wstatus) == 0) |
795 | return 0; |
796 | fprintf(stderr, "%s exited, status=%d\n" , what, WEXITSTATUS(wstatus)); |
797 | return WEXITSTATUS(wstatus); |
798 | } else if (WIFSIGNALED(wstatus)) { |
799 | xerror(fmt: "%s killed by signal %d\n" , what, WTERMSIG(wstatus)); |
800 | } else if (WIFSTOPPED(wstatus)) { |
801 | xerror(fmt: "%s stopped by signal %d\n" , what, WSTOPSIG(wstatus)); |
802 | } |
803 | |
804 | return 111; |
805 | } |
806 | |
807 | static void init_rng(void) |
808 | { |
809 | int fd = open("/dev/urandom" , O_RDONLY); |
810 | |
811 | if (fd >= 0) { |
812 | unsigned int foo; |
813 | ssize_t ret; |
814 | |
815 | /* can't fail */ |
816 | ret = read(fd, &foo, sizeof(foo)); |
817 | assert(ret == sizeof(foo)); |
818 | |
819 | close(fd); |
820 | srand(foo); |
821 | } else { |
822 | srand(time(NULL)); |
823 | } |
824 | } |
825 | |
826 | int main(int argc, char *argv[]) |
827 | { |
828 | int e1, e2, wstatus; |
829 | pid_t s, c, ret; |
830 | int pipefds[2]; |
831 | |
832 | parse_opts(argc, argv); |
833 | |
834 | init_rng(); |
835 | |
836 | e1 = pipe(pipefds); |
837 | if (e1 < 0) |
838 | die_perror(msg: "pipe" ); |
839 | |
840 | s = xfork(); |
841 | if (s == 0) |
842 | return server(pipefd: pipefds[1]); |
843 | |
844 | close(pipefds[1]); |
845 | |
846 | /* wait until server bound a socket */ |
847 | e1 = read(pipefds[0], &e1, 4); |
848 | assert(e1 == 4); |
849 | |
850 | c = xfork(); |
851 | if (c == 0) |
852 | return client(pipefd: pipefds[0]); |
853 | |
854 | close(pipefds[0]); |
855 | |
856 | ret = waitpid(s, &wstatus, 0); |
857 | if (ret == -1) |
858 | die_perror(msg: "waitpid" ); |
859 | e1 = rcheck(wstatus, what: "server" ); |
860 | ret = waitpid(c, &wstatus, 0); |
861 | if (ret == -1) |
862 | die_perror(msg: "waitpid" ); |
863 | e2 = rcheck(wstatus, what: "client" ); |
864 | |
865 | return e1 ? e1 : e2; |
866 | } |
867 | |