692dbd2c3d
* net.c (print_get_error): New function decoding error number returned as option value for SO_ERROR option. (print_getsockopt) <case SO_ERROR>: Call print_get_error. * tests/so_error.c: New test. * tests/gen_tests.in (so_error): Likewise. * tests/pure_executables.list: Add so_error. * tests/.gitignore: Likewise. Signed-off-by: Masatake YAMATO <yamato@redhat.com> Signed-off-by: Dmitry V. Levin <ldv@altlinux.org>
104 lines
2.5 KiB
C
104 lines
2.5 KiB
C
/*
|
|
* Check decoding of SO_ERROR socket option.
|
|
*
|
|
* Copyright (c) 2018 Masatake YAMATO <yamato@redhat.com>
|
|
* All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "tests.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/in.h>
|
|
#include <stdio.h>
|
|
#include <sys/select.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
static in_port_t
|
|
reserve_ephemeral_port(void)
|
|
{
|
|
int sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (sd < 0)
|
|
perror_msg_and_skip("server socket AF_UNIX SOCK_STREAM");
|
|
|
|
struct sockaddr_in addr = {
|
|
.sin_family = AF_INET,
|
|
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
|
};
|
|
|
|
/*
|
|
* The range is defined in /proc/sys/net/ipv4/ip_local_port_range.
|
|
* We use the default range here.
|
|
*/
|
|
for (in_port_t port = 49152; port < 61000; port++) {
|
|
/* Just bind here. No listen. */
|
|
addr.sin_port = htons(port);
|
|
if (bind(sd, &addr, sizeof(addr)) == 0)
|
|
return port;
|
|
}
|
|
error_msg_and_skip("no ephemeral port available for test purposes");
|
|
}
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
in_port_t port = reserve_ephemeral_port ();
|
|
|
|
/*
|
|
* Connect to the reserved port in NONBLOCK mode.
|
|
* The port is reserved but not listened. So
|
|
* the client doing "connect" gets error asynchronously.
|
|
*/
|
|
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (fd < 0)
|
|
perror_msg_and_skip("socket AF_UNIX SOCK_STREAM");
|
|
|
|
int flag = fcntl(fd, F_GETFL);
|
|
if (flag < 0)
|
|
perror_msg_and_skip("fcntl F_GETFL");
|
|
flag |= O_NONBLOCK;
|
|
if (fcntl(fd, F_SETFL, flag) < 0)
|
|
perror_msg_and_skip("fcntl F_SETFL");
|
|
|
|
struct sockaddr_in addr = {
|
|
.sin_family = AF_INET,
|
|
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
|
.sin_port = htons(port),
|
|
};
|
|
if (connect(fd, &addr, sizeof(addr)) == 0)
|
|
error_msg_and_skip("connect unexpectedly succeeded");
|
|
if (errno != EINPROGRESS)
|
|
perror_msg_and_skip("connect failed for unexpected reason");
|
|
|
|
struct timeval to = {
|
|
.tv_sec = 1,
|
|
.tv_usec = 0,
|
|
};
|
|
fd_set wfds;
|
|
FD_ZERO(&wfds);
|
|
FD_SET(fd, &wfds);
|
|
if (select(fd + 1, NULL, &wfds, NULL, &to) < 0)
|
|
perror_msg_and_skip("select");
|
|
|
|
int sock_errno;
|
|
socklen_t optlen = sizeof(sock_errno);
|
|
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sock_errno, &optlen) < 0)
|
|
perror_msg_and_skip("getsockopt");
|
|
if (sock_errno != ECONNREFUSED) {
|
|
errno = sock_errno;
|
|
perror_msg_and_skip("unexpected socket error");
|
|
}
|
|
if (optlen != sizeof(sock_errno))
|
|
error_msg_and_skip("unexpected data size for error option: %d",
|
|
optlen);
|
|
|
|
printf("getsockopt(%d, SOL_SOCKET, SO_ERROR, [ECONNREFUSED], [%u]) = 0\n",
|
|
fd, optlen);
|
|
puts("+++ exited with 0 +++");
|
|
return 0;
|
|
}
|