1 /*
2 * Check decoding of SO_ERROR socket option.
3 *
4 * Copyright (c) 2018 Masatake YAMATO <yamato@redhat.com>
5 * Copyright (c) 2018-2022 The strace developers.
6 * All rights reserved.
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11 #include "tests.h"
12
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <netinet/in.h>
16 #include <stdio.h>
17 #include <sys/select.h>
18 #include <sys/socket.h>
19 #include <sys/types.h>
20 #include <unistd.h>
21
22 static in_port_t
23 reserve_ephemeral_port(void)
24 {
25 int sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
26 if (sd < 0)
27 perror_msg_and_skip("server socket AF_UNIX SOCK_STREAM");
28
29 struct sockaddr_in addr = {
30 .sin_family = AF_INET,
31 .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
32 };
33
34 /*
35 * The range is defined in /proc/sys/net/ipv4/ip_local_port_range.
36 * We use the default range here.
37 */
38 for (in_port_t port = 49152; port < 61000; port++) {
39 /* Just bind here. No listen. */
40 addr.sin_port = htons(port);
41 if (bind(sd, (void *) &addr, sizeof(addr)) == 0)
42 return port;
43 }
44 error_msg_and_skip("no ephemeral port available for test purposes");
45 }
46
47 int
48 main(void)
49 {
50 static const int sizes[] = {
51 -1, 0, 1,
52 sizeof(int) - 1,
53 sizeof(int),
54 sizeof(int) + 1,
55 };
56
57 TAIL_ALLOC_OBJECT_CONST_PTR(int, sock_errno);
58 in_port_t port = reserve_ephemeral_port();
59 const struct sockaddr_in addr = {
60 .sin_family = AF_INET,
61 .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
62 .sin_port = htons(port),
63 };
64
65 for (size_t i = 0; i < ARRAY_SIZE(sizes); i++) {
66 /*
67 * Connect to the reserved port in NONBLOCK mode.
68 * The port is reserved but not listened. So
69 * the client doing "connect" gets error asynchronously.
70 */
71 int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
72 if (fd < 0)
73 perror_msg_and_skip("socket AF_UNIX SOCK_STREAM");
74
75 int flag = fcntl(fd, F_GETFL);
76 if (flag < 0)
77 perror_msg_and_skip("fcntl F_GETFL");
78 flag |= O_NONBLOCK;
79 if (fcntl(fd, F_SETFL, flag) < 0)
80 perror_msg_and_skip("fcntl F_SETFL");
81
82 if (connect(fd, (void *) &addr, sizeof(addr)) == 0)
83 error_msg_and_skip("connect unexpectedly succeeded");
84 if (errno != EINPROGRESS)
85 perror_msg_and_skip("connect failed for unexpected reason");
86
87 struct timeval to = {
88 .tv_sec = 1,
89 .tv_usec = 0,
90 };
91 fd_set wfds;
92 FD_ZERO(&wfds);
93 FD_SET(fd, &wfds);
94 if (select(fd + 1, NULL, &wfds, NULL, &to) < 0)
95 perror_msg_and_skip("select");
96
97 *sock_errno = 0xbadc0ded;
98 socklen_t optlen = sizes[i];
99 long rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, sock_errno,
100 &optlen);
101 const char *errstr = sprintrc(rc);
102 if (sizes[i] > 0 && rc < 0)
103 perror_msg_and_skip("getsockopt");
104 if (sizes[i] >= (int) sizeof(*sock_errno)
105 && *sock_errno != ECONNREFUSED) {
106 errno = *sock_errno;
107 perror_msg_and_skip("unexpected socket error");
108 }
109 if (sizes[i] >= (int) sizeof(*sock_errno)
110 && optlen != sizeof(*sock_errno)) {
111 error_msg_and_skip("unexpected data size for error"
112 " option: %d", optlen);
113 }
114
115 printf("getsockopt(%d, SOL_SOCKET, SO_ERROR, ", fd);
116 if (sizes[i] <= 0) {
117 printf("%p, [%d]", sock_errno, sizes[i]);
118 } else if (sizes[i] < (int) sizeof(*sock_errno)) {
119 print_quoted_hex(sock_errno, sizes[i]);
120 printf(", [%u]", sizes[i]);
121 } else if (sizes[i] == sizeof(*sock_errno)) {
122 printf("[ECONNREFUSED], [%zu]", sizeof(*sock_errno));
123 } else {
124 printf("[ECONNREFUSED], [%u => %zu]",
125 sizes[i], sizeof(*sock_errno));
126 }
127 printf(") = %s\n", errstr);
128
129 close(fd);
130 }
131
132 puts("+++ exited with 0 +++");
133 return 0;
134 }