minix/minix/tests/common-socket.c
Erik van der Kouwe 294d159017 Add new tests 80 (TCP) and 81 (UDP)
These new tests are largely based on the code from test 56 (UDS). Common code
is moved into a separate file common-socket.c. In some instances the tests
are too strict for TCP/UDP sockets, which may not always react instantly to
whatever happens on the other side (even locally). For these cases, the
ignore_* fields in struct socket_test_info indicate that there needs to be
an exception. There are also tests where it seems the functionality of inet
is either incorrect or incomplete with regard to the POSIX standard. In these
cases, the bug_* fields are used to document the issues while avoiding
failure of the test.

Change-Id: Ia860deb4559d42608790451936b1aade866faebc
2015-07-08 09:46:56 +02:00

2122 lines
48 KiB
C

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "common.h"
#include "common-socket.h"
#define ISO8601_FORMAT "%Y-%m-%dT%H:%M:%S"
/* timestamps for debug and error logs */
static char *get_timestamp(void)
{
struct tm *tm;
time_t t;
size_t len;
char *s;
len = sizeof(char) * 32;
t = time(NULL);
if (t == -1) {
return NULL;
}
tm = gmtime(&t);
if (tm == NULL) {
return NULL;
}
s = (char *) malloc(len);
if (!s) {
perror("malloc");
return NULL;
}
memset(s, '\0', len);
strftime(s, len - 1, ISO8601_FORMAT, tm);
return s;
}
void test_fail_fl(char *msg, char *file, int line)
{
char *timestamp;
timestamp = get_timestamp();
if (errct == 0) fprintf(stderr, "\n");
fprintf(stderr, "[ERROR][%s] (%s Line %d) %s [pid=%d:errno=%d:%s]\n",
timestamp, file, line, msg, getpid(),
errno, strerror(errno));
fflush(stderr);
if (timestamp != NULL) {
free(timestamp);
timestamp = NULL;
}
e(7);
}
#if DEBUG == 1
void debug_fl(char *msg, char *file, int line)
{
char *timestamp;
timestamp = get_timestamp();
fprintf(stdout,"[DEBUG][%s] (%s:%d) %s [pid=%d]\n",
timestamp, __FILE__, __LINE__, msg, getpid());
fflush(stdout);
if (timestamp != NULL) {
free(timestamp);
timestamp = NULL;
}
}
#endif
void test_socket(const struct socket_test_info *info)
{
struct stat statbuf, statbuf2;
int sd, sd2;
int rc;
int i;
debug("entering test_socket()");
debug("Test socket() with an unsupported address family");
errno = 0;
sd = socket(-1, info->type, 0);
if (!(sd == -1 && errno == EAFNOSUPPORT)) {
test_fail("socket");
if (sd != -1) {
CLOSE(sd);
}
}
debug("Test socket() with all available FDs open by this process");
for (i = 3; i < getdtablesize(); i++) {
rc = open("/dev/null", O_RDONLY);
if (rc == -1) {
test_fail("we couldn't open /dev/null for read");
}
}
errno = 0;
sd = socket(info->domain, info->type, 0);
if (!(sd == -1 && errno == EMFILE)) {
test_fail("socket() call with all fds open should fail");
if (sd != -1) {
CLOSE(sd);
}
}
for (i = 3; i < getdtablesize(); i++) {
CLOSE(i);
}
debug("Test socket() with an mismatched protocol");
errno = 0;
sd = socket(info->domain, info->type, 4);
if (!(sd == -1 && errno == EPROTONOSUPPORT)) {
test_fail("socket() should fail with errno = EPROTONOSUPPORT");
if (sd != -1) {
CLOSE(sd);
}
}
debug("Test socket() success");
/*
* open 2 sockets at once and *then* close them.
* This will test that /dev/uds is cloning properly.
*/
SOCKET(sd, info->domain, info->type, 0);
SOCKET(sd2, info->domain, info->type, 0);
rc = fstat(sd, &statbuf);
if (rc == -1) {
test_fail("fstat failed on sd");
}
rc = fstat(sd2, &statbuf2);
if (rc == -1) {
test_fail("fstat failed on sd2");
}
if (statbuf.st_dev == statbuf2.st_dev) {
test_fail("/dev/uds isn't being cloned");
}
CLOSE(sd2);
CLOSE(sd);
debug("leaving test_socket()");
}
void test_getsockname(const struct socket_test_info *info)
{
int sd;
int rc;
struct sockaddr_storage sock_addr;
socklen_t sock_addr_len;
SOCKET(sd, info->domain, info->type, 0);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc == -1) {
test_fail("bind() should have worked");
}
debug("Test getsockname() success");
memset(&sock_addr, '\0', sizeof(sock_addr));
sock_addr_len = sizeof(sock_addr);
rc = getsockname(sd, (struct sockaddr *) &sock_addr, &sock_addr_len);
if (rc == -1) {
test_fail("getsockname() should have worked");
}
info->callback_check_sockaddr((struct sockaddr *) &sock_addr,
sock_addr_len, "getsockname", 1);
CLOSE(sd);
}
void test_bind(const struct socket_test_info *info)
{
struct sockaddr_storage sock_addr;
socklen_t sock_addr_len;
int sd;
int sd2;
int rc;
debug("entering test_bind()");
info->callback_cleanup();
debug("Test bind() success");
SOCKET(sd, info->domain, info->type, 0);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc == -1) {
test_fail("bind() should have worked");
}
debug("Test getsockname() success");
memset(&sock_addr, '\0', sizeof(sock_addr));
sock_addr_len = sizeof(sock_addr);
rc = getsockname(sd, (struct sockaddr *) &sock_addr, &sock_addr_len);
if (rc == -1) {
test_fail("getsockname() should have worked");
}
info->callback_check_sockaddr((struct sockaddr *) &sock_addr,
sock_addr_len, "getsockname", 1);
debug("Test bind() with a address that has already been bind()'d");
SOCKET(sd2, info->domain, info->type, 0);
errno = 0;
rc = bind(sd2, info->serveraddr, info->serveraddrlen);
if (!((rc == -1) && (errno == EADDRINUSE)) &&
!info->bug_bind_in_use) {
test_fail("bind() should have failed with EADDRINUSE");
}
CLOSE(sd2);
CLOSE(sd);
info->callback_cleanup();
if (!info->bug_bind_null) {
debug("Test bind() with a NULL address");
SOCKET(sd, info->domain, info->type, 0);
errno = 0;
rc = bind(sd, (struct sockaddr *) NULL,
sizeof(struct sockaddr_storage));
if (!((rc == -1) && (errno == EFAULT))) {
test_fail("bind() should have failed with EFAULT");
}
CLOSE(sd);
}
debug("leaving test_bind()");
}
void test_listen(const struct socket_test_info *info)
{
int rc;
debug("entering test_listen()");
debug("Test listen() with a bad file descriptor");
errno = 0;
rc = listen(-1, 0);
if (!(rc == -1 && errno == EBADF)) {
test_fail("listen(-1, 0) should have failed");
}
debug("Test listen() with a non-socket file descriptor");
errno = 0;
rc = listen(0, 0);
/* Test on errno disabled here: there's currently no telling what this
* will return. POSIX says it should be ENOTSOCK, MINIX3 libc returns
* ENOSYS, and we used to test for ENOTTY here..
*/
if (!(rc == -1)) {
test_fail("listen(0, 0) should have failed");
}
debug("leaving test_listen()");
}
void test_shutdown(const struct socket_test_info *info)
{
int how[3] = { SHUT_RD, SHUT_WR, SHUT_RDWR };
int sd;
int rc;
int i;
debug("entering test_shutdown()");
/* test for each direction (read, write, read-write) */
for (i = 0; i < 3; i++) {
if (info->bug_shutdown_read && how[i] == SHUT_RD) continue;
debug("test shutdown() with an invalid descriptor");
errno = 0;
rc = shutdown(-1, how[i]);
if (!(rc == -1 && errno == EBADF) && !info->bug_shutdown) {
test_fail("shutdown(-1, how[i]) should have failed");
}
debug("test shutdown() with a non-socket descriptor");
errno = 0;
rc = shutdown(0, how[i]);
if (!(rc == -1 && errno == ENOSYS) && !info->bug_shutdown) {
test_fail("shutdown() should have failed with ENOSYS");
}
debug("test shutdown() with a socket that is not connected");
SOCKET(sd, info->domain, info->type, 0);
errno = 0;
rc = shutdown(sd, how[i]);
if (!(rc == -1 && errno == ENOTCONN) &&
!info->bug_shutdown_not_conn &&
!info->bug_shutdown) {
test_fail("shutdown() should have failed");
}
CLOSE(sd);
}
SOCKET(sd, info->domain, info->type, 0);
errno = 0;
rc = shutdown(sd, -1);
if (!(rc == -1 && errno == ENOTCONN) &&
!info->bug_shutdown_not_conn &&
!info->bug_shutdown) {
test_fail("shutdown(sd, -1) should have failed with ENOTCONN");
}
CLOSE(sd);
debug("leaving test_shutdown()");
}
void test_close(const struct socket_test_info *info)
{
int sd, sd2;
int rc, i;
debug("entering test_close()");
info->callback_cleanup();
debug("Test close() success");
SOCKET(sd, info->domain, info->type, 0);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc != 0) {
test_fail("bind() should have worked");
}
CLOSE(sd);
debug("Close an already closed file descriptor");
errno = 0;
rc = close(sd);
if (!(rc == -1 && errno == EBADF)) {
test_fail("close(sd) should have failed with EBADF");
}
info->callback_cleanup();
debug("dup()'ing a file descriptor and closing both should work");
SOCKET(sd, info->domain, info->type, 0);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc != 0) {
test_fail("bind() should have worked");
}
errno = 0;
sd2 = dup(sd);
if (sd2 == -1) {
test_fail("dup(sd) should have worked");
} else {
CLOSE(sd2);
CLOSE(sd);
}
info->callback_cleanup();
/* Create and close a socket a bunch of times.
* If the implementation doesn't properly free the
* socket during close(), eventually socket() will
* fail when the internal descriptor table is full.
*/
for (i = 0; i < 1024; i++) {
SOCKET(sd, info->domain, info->type, 0);
CLOSE(sd);
}
debug("leaving test_close()");
}
void test_sockopts(const struct socket_test_info *info)
{
int i;
int rc;
int sd;
int option_value;
socklen_t option_len;
debug("entering test_sockopts()");
for (i = 0; i < info->typecount; i++) {
SOCKET(sd, info->domain, info->types[i], 0);
debug("Test setsockopt() works");
option_value = 0;
option_len = sizeof(option_value);
errno = 0;
rc = getsockopt(sd, SOL_SOCKET, SO_TYPE, &option_value,
&option_len);
if (rc != 0) {
test_fail("setsockopt() should have worked");
}
if (option_value != info->types[i]) {
test_fail("SO_TYPE didn't seem to work.");
}
CLOSE(sd);
}
SOCKET(sd, info->domain, info->type, 0);
debug("Test setsockopt() works");
option_value = 0;
option_len = sizeof(option_value);
errno = 0;
rc = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &option_value, &option_len);
if (rc != 0 && !info->bug_sockopt_sndbuf) {
test_fail("getsockopt() should have worked");
}
if (info->expected_sndbuf >= 0 &&
option_value != info->expected_sndbuf &&
!info->bug_sockopt_sndbuf) {
test_fail("SO_SNDBUF didn't seem to work.");
}
CLOSE(sd);
SOCKET(sd, info->domain, info->type, 0);
debug("Test setsockopt() works");
option_value = 0;
option_len = sizeof(option_value);
errno = 0;
rc = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &option_value, &option_len);
if (rc != 0 && !info->bug_sockopt_rcvbuf) {
test_fail("getsockopt() should have worked");
}
if (info->expected_rcvbuf >= 0 &&
option_value != info->expected_rcvbuf &&
!info->bug_sockopt_rcvbuf) {
test_fail("SO_RCVBUF didn't seem to work.");
}
CLOSE(sd);
debug("leaving test_sockopts()");
}
void test_read(const struct socket_test_info *info)
{
int rc;
int fd;
char buf[BUFSIZE];
debug("entering test_read()");
errno = 0;
rc = read(-1, buf, sizeof(buf));
if (!(rc == -1 && errno == EBADF)) {
test_fail("read() should have failed with EBADF");
}
fd = open("/tmp", O_RDONLY);
if (fd == -1) {
test_fail("open(\"/tmp\", O_RDONLY) should have worked");
}
CLOSE(fd);
debug("leaving test_read()");
}
void test_write(const struct socket_test_info *info)
{
int rc;
char buf[BUFSIZE];
debug("entering test_write()");
errno = 0;
rc = write(-1, buf, sizeof(buf));
if (!(rc == -1 && errno == EBADF)) {
test_fail("write() should have failed with EBADF");
}
debug("leaving test_write()");
}
void test_dup(const struct socket_test_info *info)
{
struct stat info1;
struct stat info2;
int sd, sd2;
int rc;
int i;
debug("entering test_dup()");
info->callback_cleanup();
debug("Test dup()");
SOCKET(sd, info->domain, info->type, 0);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc != 0) {
test_fail("bind() should have worked");
}
errno = 0;
sd2 = dup(sd);
if (sd2 == -1) {
test_fail("dup(sd) should have worked");
}
rc = fstat(sd, &info1);
if (rc == -1) {
test_fail("fstat(fd, &info1) failed");
}
rc = fstat(sd2, &info2);
if (rc == -1) {
test_fail("fstat(sd, &info2) failed");
}
if (info1.st_ino != info2.st_ino) {
test_fail("dup() failed info1.st_ino != info2.st_ino");
}
CLOSE(sd);
CLOSE(sd2);
debug("Test dup() with a closed socket");
errno = 0;
rc = dup(sd);
if (!(rc == -1 && errno == EBADF)) {
test_fail("dup(sd) on a closed socket shouldn't have worked");
}
debug("Test dup() with socket descriptor of -1");
errno = 0;
rc = dup(-1);
if (!(rc == -1 && errno == EBADF)) {
test_fail("dup(-1) shouldn't have worked");
}
debug("Test dup() when all of the file descriptors are taken");
SOCKET(sd, info->domain, info->type, 0);
for (i = 4; i < getdtablesize(); i++) {
rc = open("/dev/null", O_RDONLY);
if (rc == -1) {
test_fail("we couldn't open /dev/null for read");
}
}
errno = 0;
sd2 = dup(sd);
if (!(sd2 == -1 && errno == EMFILE)) {
test_fail("dup(sd) should have failed with errno = EMFILE");
}
for (i = 3; i < getdtablesize(); i++) {
CLOSE(i);
}
info->callback_cleanup();
debug("leaving test_dup()");
}
void test_dup2(const struct socket_test_info *info)
{
struct stat info1;
struct stat info2;
int sd;
int fd;
int rc;
debug("entering test_dup2()");
info->callback_cleanup();
SOCKET(sd, info->domain, info->type, 0);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc != 0) {
test_fail("bind() should have worked");
}
fd = open("/dev/null", O_RDONLY);
if (fd == -1) {
test_fail("open(\"/dev/null\", O_RDONLY) failed");
}
fd = dup2(sd, fd);
if (fd == -1) {
test_fail("dup2(sd, fd) failed.");
}
memset(&info1, '\0', sizeof(struct stat));
memset(&info2, '\0', sizeof(struct stat));
rc = fstat(fd, &info1);
if (rc == -1) {
test_fail("fstat(fd, &info1) failed");
}
rc = fstat(sd, &info2);
if (rc == -1) {
test_fail("fstat(sd, &info2) failed");
}
if (!(info1.st_ino == info2.st_ino &&
major(info1.st_dev) == major(info2.st_dev) &&
minor(info1.st_dev) == minor(info2.st_dev))) {
test_fail("dup2() failed");
}
CLOSE(fd);
CLOSE(sd);
info->callback_cleanup();
debug("leaving test_dup2()");
}
/*
* A toupper() server. This toy server converts a string to upper case.
*/
static void test_xfer_server(const struct socket_test_info *info, pid_t pid)
{
int i;
struct timeval tv;
fd_set readfds;
int status;
int rc;
int sd;
unsigned char buf[BUFSIZE];
socklen_t client_addr_size;
int client_sd;
struct sockaddr_storage client_addr;
status = 0;
rc = 0;
sd = 0;
client_sd = 0;
client_addr_size = sizeof(struct sockaddr_storage);
memset(&buf, '\0', sizeof(buf));
memset(&client_addr, '\0', sizeof(client_addr));
SOCKET(sd, info->domain, info->type, 0);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc == -1) {
test_fail("bind() should have worked");
}
rc = listen(sd, 8);
if (rc == -1) {
test_fail("listen(sd, 8) should have worked");
}
/* we're ready for connections, time to tell the client to start
* the test
*/
kill(pid, SIGUSR1);
tv.tv_sec = 10;
tv.tv_usec = 0;
FD_ZERO(&readfds);
FD_SET(sd, &readfds);
/* use select() in case the client is really broken and never
* attempts to connect (we don't want to block on accept()
* forever).
*/
rc = select(sd + 1, &readfds, NULL, NULL, &tv);
if (rc == -1) {
test_fail("[server] select() should not have failed");
}
if (rc != 1) {
test_fail("[server] select() should have returned 1");
printf("[server] select returned %d\n", rc);
}
if (!(FD_ISSET(sd, &readfds))) {
test_fail("[server] client didn't connect within 10 seconds");
kill(pid, SIGKILL);
return;
}
client_sd = accept(sd, (struct sockaddr *) &client_addr,
&client_addr_size);
if (client_sd == -1) {
test_fail("accept() should have worked");
kill(pid, SIGKILL);
return;
} else {
debug("[server] client accept()'d");
}
debug("[server] Reading message");
rc = read(client_sd, buf, sizeof(buf));
if (rc == -1) {
test_fail("read() failed unexpectedly");
kill(pid, SIGKILL);
return;
}
debug("[server] we got the following message:");
debug(buf);
for (i = 0; i < rc && i < 127; i++) {
buf[i] = toupper(buf[i]);
}
debug("[server] Writing message...");
rc = write(client_sd, buf, sizeof(buf));
if (rc == -1) {
test_fail("write(client_sd, buf, sizeof(buf)) failed");
kill(pid, SIGKILL);
return;
}
if (rc < strlen(buf)) {
test_fail("[server] write didn't write all the bytes");
}
memset(&buf, '\0', sizeof(buf));
debug("[server] Recv message");
rc = recv(client_sd, buf, sizeof(buf), 0);
if (rc == -1) {
test_fail("recv() failed unexpectedly");
kill(pid, SIGKILL);
return;
}
debug("[server] we got the following message:");
debug(buf);
for (i = 0; i < rc && i < 127; i++) {
buf[i] = toupper(buf[i]);
}
debug("[server] Sending message...");
rc = send(client_sd, buf, sizeof(buf), 0);
if (rc == -1) {
test_fail("send(client_sd, buf, sizeof(buf), 0) failed");
kill(pid, SIGKILL);
return;
}
if (rc < strlen(buf)) {
test_fail("[server] write didn't write all the bytes");
}
memset(&buf, '\0', sizeof(buf));
debug("[server] Recvfrom message");
rc = recvfrom(client_sd, buf, sizeof(buf), 0, NULL, 0);
if (rc == -1) {
test_fail("recvfrom() failed unexpectedly");
kill(pid, SIGKILL);
return;
}
debug("[server] we got the following message:");
debug(buf);
for (i = 0; i < rc && i < 127; i++) {
buf[i] = toupper(buf[i]);
}
debug("[server] Sendto message...");
rc = sendto(client_sd, buf, sizeof(buf), 0, NULL, 0);
if (rc == -1) {
test_fail("sendto() failed");
kill(pid, SIGKILL);
return;
}
if (rc < strlen(buf)) {
test_fail("[server] write didn't write all the bytes");
}
shutdown(client_sd, SHUT_RDWR);
CLOSE(client_sd);
shutdown(sd, SHUT_RDWR);
CLOSE(sd);
/* wait for client to exit */
do {
errno = 0;
rc = waitpid(pid, &status, 0);
} while (rc == -1 && errno == EINTR);
/* we use the exit status to get its error count */
errct += WEXITSTATUS(status);
}
int server_ready = 0;
/* signal handler for the client */
void test_xfer_sighdlr(int sig)
{
debug("entering signal handler");
switch (sig) {
/* the server will send SIGUSR1 when it is time for us
* to start the tests
*/
case SIGUSR1:
server_ready = 1;
debug("got SIGUSR1, the server is ready for the client");
break;
default:
debug("didn't get SIGUSR1");
}
debug("leaving signal handler");
}
/*
* A toupper() client.
*/
static void test_xfer_client(const struct socket_test_info *info)
{
struct timeval tv;
fd_set readfds;
struct sockaddr_storage peer_addr;
socklen_t peer_addr_len;
int sd;
int rc;
char buf[BUFSIZE];
debug("[client] entering test_xfer_client()");
errct = 0; /* reset error count */
memset(&buf, '\0', sizeof(buf));
while (server_ready == 0) {
debug("[client] waiting for the server to signal");
sleep(1);
}
peer_addr_len = sizeof(peer_addr);
if (info->callback_xfer_prepclient) info->callback_xfer_prepclient();
debug("[client] creating client socket");
SOCKET(sd, info->domain, info->type, 0);
debug("[client] connecting to server through the symlink");
rc = connect(sd, info->clientaddrsym, info->clientaddrsymlen);
if (rc == -1) {
test_fail("[client] connect() should have worked");
} else {
debug("[client] connected");
}
debug("[client] testing getpeername()");
memset(&peer_addr, '\0', sizeof(peer_addr));
rc = getpeername(sd, (struct sockaddr *) &peer_addr, &peer_addr_len);
if (rc == -1) {
test_fail("[client] getpeername() should have worked");
}
/* we need to use the full path "/usr/src/test/DIR_56/test.sock"
* because that is what is returned by getpeername().
*/
info->callback_check_sockaddr((struct sockaddr *) &peer_addr,
peer_addr_len, "getpeername", 1);
strncpy(buf, "Hello, World!", sizeof(buf) - 1);
debug("[client] send to server");
rc = write(sd, buf, sizeof(buf));
if (rc == -1) {
test_fail("[client] write() failed unexpectedly");
}
memset(buf, '\0', sizeof(buf));
debug("[client] read from server");
rc = read(sd, buf, sizeof(buf));
if (rc == -1) {
test_fail("[client] read() failed unexpectedly");
} else {
debug("[client] we got the following message:");
debug(buf);
}
if (strncmp(buf, "HELLO, WORLD!", sizeof(buf)) != 0) {
test_fail("[client] We didn't get the correct response");
}
memset(&buf, '\0', sizeof(buf));
strncpy(buf, "Bonjour!", sizeof(buf) - 1);
debug("[client] send to server");
rc = send(sd, buf, sizeof(buf), 0);
if (rc == -1) {
test_fail("[client] send() failed unexpectedly");
}
if (info->callback_xfer_peercred) info->callback_xfer_peercred(sd);
debug("Testing select()");
tv.tv_sec = 2;
tv.tv_usec = 500000;
FD_ZERO(&readfds);
FD_SET(sd, &readfds);
rc = select(sd + 1, &readfds, NULL, NULL, &tv);
if (rc == -1) {
test_fail("[client] select() should not have failed");
}
if (rc != 1) {
test_fail("[client] select() should have returned 1");
}
if (!(FD_ISSET(sd, &readfds))) {
test_fail("The server didn't respond within 2.5 seconds");
}
memset(buf, '\0', sizeof(buf));
debug("[client] recv from server");
rc = recv(sd, buf, sizeof(buf), 0);
if (rc == -1) {
test_fail("[client] recv() failed unexpectedly");
} else {
debug("[client] we got the following message:");
debug(buf);
}
if (strncmp(buf, "BONJOUR!", sizeof(buf)) != 0) {
test_fail("[client] We didn't get the right response.");
}
memset(&buf, '\0', sizeof(buf));
strncpy(buf, "Hola!", sizeof(buf) - 1);
debug("[client] sendto to server");
rc = sendto(sd, buf, sizeof(buf), 0, NULL, 0);
if (rc == -1) {
test_fail("[client] sendto() failed");
}
debug("Testing select()");
tv.tv_sec = 2;
tv.tv_usec = 500000;
FD_ZERO(&readfds);
FD_SET(sd, &readfds);
rc = select(sd + 1, &readfds, NULL, NULL, &tv);
if (rc == -1) {
test_fail("[client] select() should not have failed");
}
if (rc != 1) {
test_fail("[client] select() should have returned 1");
}
if (!(FD_ISSET(sd, &readfds))) {
test_fail("[client] The server didn't respond in 2.5 seconds");
}
memset(buf, '\0', sizeof(buf));
debug("[client] recvfrom from server");
rc = recvfrom(sd, buf, sizeof(buf), 0, NULL, 0);
if (rc == -1) {
test_fail("[cleint] recvfrom() failed unexpectedly");
} else {
debug("[client] we got the following message:");
debug(buf);
}
if (strncmp(buf, "HOLA!", sizeof(buf)) != 0) {
test_fail("[client] We didn't get the right response.");
}
debug("[client] closing socket");
CLOSE(sd);
debug("[client] leaving test_xfer_client()");
exit(errct);
}
void test_xfer(const struct socket_test_info *info)
{
pid_t pid;
debug("entering test_xfer()");
info->callback_cleanup();
/* the signal handler is only used by the client, but we have to
* install it now. if we don't the server may signal the client
* before the handler is installed.
*/
debug("installing signal handler");
if (signal(SIGUSR1, test_xfer_sighdlr) == SIG_ERR) {
test_fail("signal(SIGUSR1, test_xfer_sighdlr) failed");
}
debug("signal handler installed");
server_ready = 0;
pid = fork();
if (pid == -1) {
test_fail("fork() failed");
return;
} else if (pid == 0) {
debug("child");
errct = 0;
test_xfer_client(info);
test_fail("we should never get here");
exit(1);
} else {
debug("parent");
test_xfer_server(info, pid);
debug("parent done");
}
info->callback_cleanup();
debug("leaving test_xfer()");
}
static void test_simple_client(const struct socket_test_info *info, int type)
{
char buf[BUFSIZE];
int sd, rc;
sd = socket(info->domain, type, 0);
if (sd == -1) {
test_fail("socket");
exit(errct);
}
while (server_ready == 0) {
debug("[client] waiting for the server");
sleep(1);
}
bzero(buf, BUFSIZE);
snprintf(buf, BUFSIZE-1, "Hello, My Name is Client.");
if (type == SOCK_DGRAM) {
rc = sendto(sd, buf, strlen(buf) + 1, 0,
info->clientaddr, info->clientaddrlen);
if (rc == -1) {
test_fail("sendto");
exit(errct);
}
} else {
rc = connect(sd, info->clientaddr, info->clientaddrlen);
if (rc == -1) {
test_fail("connect");
exit(errct);
}
rc = write(sd, buf, strlen(buf) + 1);
if (rc == -1) {
test_fail("write");
}
memset(buf, '\0', BUFSIZE);
rc = read(sd, buf, BUFSIZE);
if (rc == -1) {
test_fail("read");
}
if (strcmp("Hello, My Name is Server.", buf) != 0) {
test_fail("didn't read the correct string");
}
}
rc = close(sd);
if (rc == -1) {
test_fail("close");
}
exit(errct);
}
static void test_simple_server(const struct socket_test_info *info, int type,
pid_t pid)
{
char buf[BUFSIZE];
int sd, rc, client_sd, status;
struct sockaddr_storage addr;
socklen_t addr_len;
addr_len = info->clientaddrlen;
sd = socket(info->domain, type, 0);
if (sd == -1) {
test_fail("socket");
}
assert(info->clientaddrlen <= sizeof(addr));
memcpy(&addr, info->clientaddr, info->clientaddrlen);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc == -1) {
test_fail("bind");
}
if (type == SOCK_DGRAM) {
/* ready for client */
kill(pid, SIGUSR1);
rc = recvfrom(sd, buf, BUFSIZE, 0,
(struct sockaddr *) &addr, &addr_len);
if (rc == -1) {
test_fail("recvfrom");
}
} else {
rc = listen(sd, 5);
if (rc == -1) {
test_fail("listen");
}
/* we're ready for connections, time to tell the client
* to start the test
*/
kill(pid, SIGUSR1);
client_sd = accept(sd, (struct sockaddr *) &addr, &addr_len);
if (client_sd == -1) {
test_fail("accept");
}
memset(buf, '\0', BUFSIZE);
rc = read(client_sd, buf, BUFSIZE);
if (rc == -1) {
test_fail("read");
}
if (strcmp("Hello, My Name is Client.", buf) != 0) {
test_fail("didn't read the correct string");
}
/* added for extra fun to make the client block on read() */
sleep(1);
bzero(buf, BUFSIZE);
snprintf(buf, BUFSIZE-1, "Hello, My Name is Server.");
rc = write(client_sd, buf, strlen(buf) + 1);
if (rc == -1) {
test_fail("write");
}
rc = close(client_sd);
if (rc == -1) {
test_fail("close");
}
}
rc = close(sd);
if (rc == -1) {
test_fail("close");
}
/* wait for client to exit */
do {
errno = 0;
rc = waitpid(pid, &status, 0);
} while (rc == -1 && errno == EINTR);
/* we use the exit status to get its error count */
errct += WEXITSTATUS(status);
}
static void test_abort_client(const struct socket_test_info *info,
int abort_type);
static void test_abort_server(const struct socket_test_info *info,
pid_t pid, int abort_type);
void test_abort_client_server(const struct socket_test_info *info,
int abort_type)
{
pid_t pid;
debug("test_simple_client_server()");
info->callback_cleanup();
/* the signal handler is only used by the client, but we have to
* install it now. if we don't the server may signal the client
* before the handler is installed.
*/
debug("installing signal handler");
if (signal(SIGUSR1, test_xfer_sighdlr) == SIG_ERR) {
test_fail("signal(SIGUSR1, test_xfer_sighdlr) failed");
}
debug("signal handler installed");
server_ready = 0;
pid = fork();
if (pid == -1) {
test_fail("fork() failed");
return;
} else if (pid == 0) {
debug("child");
errct = 0;
test_abort_client(info, abort_type);
test_fail("we should never get here");
exit(1);
} else {
debug("parent");
test_abort_server(info, pid, abort_type);
debug("parent done");
}
info->callback_cleanup();
}
static void test_abort_client(const struct socket_test_info *info,
int abort_type)
{
char buf[BUFSIZE];
int sd, rc;
sd = socket(info->domain, info->type, 0);
if (sd == -1) {
test_fail("socket");
exit(errct);
}
while (server_ready == 0) {
debug("[client] waiting for the server");
sleep(1);
}
bzero(buf, BUFSIZE);
snprintf(buf, BUFSIZE-1, "Hello, My Name is Client.");
rc = connect(sd, info->clientaddr, info->clientaddrlen);
if (rc == -1) {
test_fail("connect");
exit(errct);
}
if (abort_type == 2) {
/* Give server a chance to close connection */
sleep(2);
rc = write(sd, buf, strlen(buf) + 1);
if (rc != -1) {
if (!info->ignore_write_conn_reset) {
test_fail("write should have failed\n");
}
} else if (errno != ECONNRESET) {
test_fail("errno should've been ECONNRESET\n");
}
}
rc = close(sd);
if (rc == -1) {
test_fail("close");
}
exit(errct);
}
static void test_abort_server(const struct socket_test_info *info,
pid_t pid, int abort_type)
{
char buf[BUFSIZE];
int sd, rc, client_sd, status;
struct sockaddr_storage addr;
socklen_t addr_len;
addr_len = info->clientaddrlen;
sd = socket(info->domain, info->type, 0);
if (sd == -1) {
test_fail("socket");
}
assert(sizeof(addr) >= info->clientaddrlen);
memcpy(&addr, info->clientaddr, info->clientaddrlen);
rc = bind(sd, info->serveraddr, info->serveraddrlen);
if (rc == -1) {
test_fail("bind");
}
rc = listen(sd, 5);
if (rc == -1) {
test_fail("listen");
}
/* we're ready for connections, time to tell the client
* to start the test
*/
kill(pid, SIGUSR1);
client_sd = accept(sd, (struct sockaddr *) &addr, &addr_len);
if (client_sd == -1) {
test_fail("accept");
}
if (abort_type == 1) {
memset(buf, '\0', BUFSIZE);
rc = read(client_sd, buf, BUFSIZE);
if (rc != -1 && (rc != 0 || !info->ignore_read_conn_reset)) {
test_fail("read should've failed or returned zero\n");
}
if (rc != 0 && errno != ECONNRESET) {
test_fail("errno should've been ECONNRESET\n");
}
} /* else if (abort_type == 2) { */
rc = close(client_sd);
if (rc == -1) {
test_fail("close");
}
/* } */
rc = close(sd);
if (rc == -1) {
test_fail("close");
}
/* wait for client to exit */
do {
errno = 0;
rc = waitpid(pid, &status, 0);
} while (rc == -1 && errno == EINTR);
/* we use the exit status to get its error count */
errct += WEXITSTATUS(status);
}
void test_simple_client_server(const struct socket_test_info *info, int type)
{
pid_t pid;
debug("entering test_simple_client_server()");
info->callback_cleanup();
/* the signal handler is only used by the client, but we have to
* install it now. if we don't the server may signal the client
* before the handler is installed.
*/
debug("installing signal handler");
if (signal(SIGUSR1, test_xfer_sighdlr) == SIG_ERR) {
test_fail("signal(SIGUSR1, test_xfer_sighdlr) failed");
}
debug("signal handler installed");
server_ready = 0;
pid = fork();
if (pid == -1) {
test_fail("fork() failed");
return;
} else if (pid == 0) {
debug("child");
errct = 0;
test_simple_client(info, type);
test_fail("we should never get here");
exit(1);
} else {
debug("parent");
test_simple_server(info, type, pid);
debug("parent done");
}
info->callback_cleanup();
debug("leaving test_simple_client_server()");
}
void test_msg_dgram(const struct socket_test_info *info)
{
int rc;
int src;
int dst;
struct sockaddr_storage addr;
struct iovec iov[3];
struct msghdr msg1;
struct msghdr msg2;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
debug("entering test_msg_dgram");
info->callback_cleanup();
src = socket(info->domain, SOCK_DGRAM, 0);
if (src == -1) {
test_fail("socket");
}
dst = socket(info->domain, SOCK_DGRAM, 0);
if (dst == -1) {
test_fail("socket");
}
rc = bind(src, info->serveraddr2, info->serveraddr2len);
if (rc == -1) {
test_fail("bind");
}
assert(info->clientaddrlen <= sizeof(addr));
memcpy(&addr, info->clientaddr, info->clientaddrlen);
rc = bind(dst, info->serveraddr, info->serveraddrlen);
if (rc == -1) {
test_fail("bind");
}
memset(&buf1, '\0', BUFSIZE);
memset(&buf2, '\0', BUFSIZE);
memset(&buf3, '\0', BUFSIZE);
strncpy(buf1, "Minix ", BUFSIZE-1);
strncpy(buf2, "is ", BUFSIZE-1);
strncpy(buf3, "great!", BUFSIZE-1);
iov[0].iov_base = buf1;
iov[0].iov_len = 6;
iov[1].iov_base = buf2;
iov[1].iov_len = 3;
iov[2].iov_base = buf3;
iov[2].iov_len = 32;
memset(&msg1, '\0', sizeof(struct msghdr));
msg1.msg_name = &addr;
msg1.msg_namelen = info->clientaddrlen;
msg1.msg_iov = iov;
msg1.msg_iovlen = 3;
msg1.msg_control = NULL;
msg1.msg_controllen = 0;
msg1.msg_flags = 0;
rc = sendmsg(src, &msg1, 0);
if (rc == -1) {
test_fail("sendmsg");
}
memset(&buf1, '\0', BUFSIZE);
memset(&buf2, '\0', BUFSIZE);
iov[0].iov_base = buf1;
iov[0].iov_len = 9;
iov[1].iov_base = buf2;
iov[1].iov_len = 32;
memset(&addr, '\0', sizeof(addr));
memset(&msg2, '\0', sizeof(struct msghdr));
msg2.msg_name = &addr;
msg2.msg_namelen = sizeof(addr);
msg2.msg_iov = iov;
msg2.msg_iovlen = 2;
msg2.msg_control = NULL;
msg2.msg_controllen = 0;
msg2.msg_flags = 0;
rc = recvmsg(dst, &msg2, 0);
if (rc == -1) {
test_fail("recvmsg");
}
if (strncmp(buf1, "Minix is ", 9) || strncmp(buf2, "great!", 6)) {
test_fail("recvmsg");
}
/* we need to use the full path "/usr/src/test/DIR_56/testb.sock"
* because that is what is returned by recvmsg().
*/
info->callback_check_sockaddr((struct sockaddr *) &addr,
msg2.msg_namelen, "recvmsg", 2);
rc = close(dst);
if (rc == -1) {
test_fail("close");
}
rc = close(src);
if (rc == -1) {
test_fail("close");
}
info->callback_cleanup();
debug("leaving test_msg_dgram");
}
#define check_select(sd, rd, wr, block) \
check_select_internal(sd, rd, wr, block, 1, __LINE__)
#define check_select_cond(sd, rd, wr, block, allchecks) \
check_select_internal(sd, rd, wr, block, allchecks, __LINE__)
static void
check_select_internal(int sd, int rd, int wr, int block, int allchecks, int line)
{
fd_set read_set, write_set;
struct timeval tv;
FD_ZERO(&read_set);
if (rd != -1)
FD_SET(sd, &read_set);
FD_ZERO(&write_set);
if (wr != -1)
FD_SET(sd, &write_set);
tv.tv_sec = block ? 2 : 0;
tv.tv_usec = 0;
errno = 0;
if (select(sd + 1, &read_set, &write_set, NULL, &tv) < 0)
test_fail_fl("select() failed unexpectedly", __FILE__, line);
if (rd != -1 && !!FD_ISSET(sd, &read_set) != rd && allchecks)
test_fail_fl("select() mismatch on read operation",
__FILE__, line);
if (wr != -1 && !!FD_ISSET(sd, &write_set) != wr && allchecks)
test_fail_fl("select() mismatch on write operation",
__FILE__, line);
}
/*
* Verify that:
* - a nonblocking connecting socket for which there is no accepter, will
* return EINPROGRESS and complete in the background later;
* - a nonblocking listening socket will return EAGAIN on accept;
* - connecting a connecting socket yields EALREADY;
* - connecting a connected socket yields EISCONN;
* - selecting for read and write on a connecting socket will only satisfy the
* write only once it is connected;
* - doing a nonblocking write on a connecting socket yields EAGAIN;
* - doing a nonblocking read on a connected socket with no pending data yields
* EAGAIN.
*/
void
test_nonblock(const struct socket_test_info *info)
{
char buf[BUFSIZE];
socklen_t len;
int server_sd, client_sd;
struct sockaddr_storage addr;
int status;
debug("entering test_nonblock()");
memset(buf, 0, sizeof(buf));
SOCKET(server_sd, info->domain, info->type, 0);
if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1)
test_fail("bind() should have worked");
if (listen(server_sd, 8) == -1)
test_fail("listen() should have worked");
fcntl(server_sd, F_SETFL, fcntl(server_sd, F_GETFL) | O_NONBLOCK);
check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/);
len = sizeof(addr);
if (accept(server_sd, (struct sockaddr *) &addr, &len) != -1 ||
errno != EAGAIN)
test_fail("accept() should have yielded EAGAIN");
SOCKET(client_sd, info->domain, info->type, 0);
fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK);
if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1) {
test_fail("connect() should have failed");
} else if (errno != EINPROGRESS) {
test_fail("connect() should have yielded EINPROGRESS");
}
check_select_cond(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/,
!info->ignore_select_delay);
if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1) {
test_fail("connect() should have failed");
} else if (errno != EALREADY && errno != EISCONN) {
test_fail("connect() should have yielded EALREADY");
}
if (recv(client_sd, buf, sizeof(buf), 0) != -1 || errno != EAGAIN)
test_fail("recv() should have yielded EAGAIN");
/* This may be an implementation aspect, or even plain wrong (?). */
if (send(client_sd, buf, sizeof(buf), 0) != -1) {
if (!info->ignore_send_waiting) {
test_fail("send() should have failed");
}
} else if (errno != EAGAIN) {
test_fail("send() should have yielded EAGAIN");
}
switch (fork()) {
case 0:
errct = 0;
close(client_sd);
check_select(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/);
len = sizeof(addr);
client_sd = accept(server_sd, (struct sockaddr *) &addr, &len);
if (client_sd == -1)
test_fail("accept() should have succeeded");
check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/);
close(server_sd);
/* Let the socket become writable in the parent process. */
sleep(1);
if (write(client_sd, buf, 1) != 1)
test_fail("write() should have succeeded");
/* Wait for the client side to close. */
check_select_cond(client_sd, 0 /*read*/, 1 /*write*/,
0 /*block*/, !info->ignore_select_delay /*allchecks*/);
check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/);
check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/);
exit(errct);
case -1:
test_fail("can't fork");
default:
break;
}
close(server_sd);
check_select(client_sd, 0 /*read*/, 1 /*write*/, 1 /*block*/);
check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/);
if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1 ||
errno != EISCONN)
test_fail("connect() should have yielded EISCONN");
check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/);
check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/);
if (read(client_sd, buf, 1) != 1)
test_fail("read() should have succeeded");
check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/);
if (read(client_sd, buf, 1) != -1 || errno != EAGAIN)
test_fail("read() should have yielded EAGAIN");
/* Let the child process block on the select waiting for the close. */
sleep(1);
close(client_sd);
errno = 0;
if (wait(&status) <= 0)
test_fail("wait() should have succeeded");
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
test_fail("child process failed the test");
info->callback_cleanup();
debug("leaving test_nonblock()");
}
/*
* Verify that a nonblocking connect for which there is an accepter, succeeds
* immediately. A pretty lame test, only here for completeness.
*/
void
test_connect_nb(const struct socket_test_info *info)
{
socklen_t len;
int server_sd, client_sd;
struct sockaddr_storage addr;
int status;
debug("entering test_connect_nb()");
SOCKET(server_sd, info->domain, info->type, 0);
if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1)
test_fail("bind() should have worked");
if (listen(server_sd, 8) == -1)
test_fail("listen() should have worked");
switch (fork()) {
case 0:
errct = 0;
len = sizeof(addr);
if (accept(server_sd, (struct sockaddr *) &addr, &len) == -1)
test_fail("accept() should have succeeded");
exit(errct);
case -1:
test_fail("can't fork");
default:
break;
}
close(server_sd);
sleep(1);
SOCKET(client_sd, info->domain, info->type, 0);
fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK);
if (connect(client_sd, info->clientaddr, info->clientaddrlen) != 0) {
if (!info->ignore_connect_delay) {
test_fail("connect() should have succeeded");
} else if (errno != EINPROGRESS) {
test_fail("connect() should have succeeded or "
"failed with EINPROGRESS");
}
}
close(client_sd);
if (wait(&status) <= 0)
test_fail("wait() should have succeeded");
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
test_fail("child process failed the test");
info->callback_cleanup();
debug("leaving test_connect_nb()");
}
static void
dummy_handler(int sig)
{
/* Nothing. */
}
/*
* Verify that:
* - interrupting a blocking connect will return EINTR but complete in the
* background later;
* - doing a blocking write on an asynchronously connecting socket succeeds
* once the socket is connected.
* - doing a nonblocking write on a connected socket with lots of pending data
* yields EAGAIN.
*/
void
test_intr(const struct socket_test_info *info)
{
struct sigaction act, oact;
char buf[BUFSIZE];
int isconn;
socklen_t len;
int server_sd, client_sd;
struct sockaddr_storage addr;
int r, status;
debug("entering test_intr()");
memset(buf, 0, sizeof(buf));
SOCKET(server_sd, info->domain, info->type, 0);
if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1)
test_fail("bind() should have worked");
if (listen(server_sd, 8) == -1)
test_fail("listen() should have worked");
SOCKET(client_sd, info->domain, info->type, 0);
memset(&act, 0, sizeof(act));
act.sa_handler = dummy_handler;
if (sigaction(SIGALRM, &act, &oact) == -1)
test_fail("sigaction() should have succeeded");
if (info->domain != PF_INET) alarm(1);
isconn = 0;
if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1) {
if (!info->ignore_connect_unaccepted) {
test_fail("connect() should have failed");
}
isconn = 1;
} else if (errno != EINTR) {
test_fail("connect() should have yielded EINTR");
}
alarm(0);
check_select(client_sd, 0 /*read*/, isconn /*write*/, 0 /*block*/);
switch (fork()) {
case 0:
errct = 0;
close(client_sd);
check_select(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/);
len = sizeof(addr);
client_sd = accept(server_sd, (struct sockaddr *) &addr, &len);
if (client_sd == -1)
test_fail("accept() should have succeeded");
check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/);
close(server_sd);
check_select(client_sd, 1 /*read*/, -1 /*write*/, 1 /*block*/);
check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/);
if (recv(client_sd, buf, sizeof(buf), 0) != sizeof(buf))
test_fail("recv() should have yielded bytes");
/* No partial transfers should be happening. */
check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/);
sleep(1);
fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) |
O_NONBLOCK);
/* We can only test nonblocking writes by filling the pipe. */
while ((r = write(client_sd, buf, sizeof(buf))) > 0);
if (r != -1) {
test_fail("write() should have failed");
} else if (errno != EAGAIN) {
test_fail("write() should have yielded EAGAIN");
}
check_select(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/);
if (write(client_sd, buf, 1) != -1) {
test_fail("write() should have failed");
} else if (errno != EAGAIN) {
test_fail("write() should have yielded EAGAIN");
}
exit(errct);
case -1:
test_fail("can't fork");
default:
break;
}
close(server_sd);
if (send(client_sd, buf, sizeof(buf), 0) != sizeof(buf))
test_fail("send() should have succeded");
check_select(client_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/);
if (wait(&status) <= 0)
test_fail("wait() should have succeeded");
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
test_fail("child process failed the test");
check_select(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/);
close(client_sd);
sigaction(SIGALRM, &oact, NULL);
info->callback_cleanup();
debug("leaving test_intr()");
}
/*
* Verify that closing a connecting socket before it is accepted will result in
* no activity on the accepting side later.
*/
void
test_connect_close(const struct socket_test_info *info)
{
int server_sd, client_sd;
struct sockaddr_storage addr;
socklen_t len;
debug("entering test_connect_close()");
SOCKET(server_sd, info->domain, info->type, 0);
if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1)
test_fail("bind() should have worked");
if (listen(server_sd, 8) == -1)
test_fail("listen() should have worked");
fcntl(server_sd, F_SETFL, fcntl(server_sd, F_GETFL) | O_NONBLOCK);
check_select(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/);
SOCKET(client_sd, info->domain, info->type, 0);
fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK);
if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1 ||
errno != EINPROGRESS)
test_fail("connect() should have yielded EINPROGRESS");
check_select_cond(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/,
!info->ignore_select_delay);
check_select_cond(server_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/,
!info->ignore_select_delay);
close(client_sd);
check_select_cond(server_sd, 0 /*read*/, 1 /*write*/, 0 /*block*/,
!info->ignore_select_delay);
len = sizeof(addr);
errno = 0;
if (accept(server_sd, (struct sockaddr *) &addr, &len) != -1) {
if (!info->ignore_accept_delay) {
test_fail("accept() should have failed");
}
} else if (errno != EAGAIN) {
test_fail("accept() should have yielded EAGAIN");
}
close(server_sd);
info->callback_cleanup();
debug("leaving test_connect_close()");
}
/*
* Verify that closing a listening socket will cause a blocking connect to fail
* with ECONNRESET, and that a subsequent write will yield EPIPE.
*/
void
test_listen_close(const struct socket_test_info *info)
{
int server_sd, client_sd;
int status;
char byte;
debug("entering test_listen_close()");
SOCKET(server_sd, info->domain, info->type, 0);
if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1)
test_fail("bind() should have worked");
if (listen(server_sd, 8) == -1)
test_fail("listen() should have worked");
switch (fork()) {
case 0:
sleep(1);
exit(0);
case -1:
test_fail("can't fork");
default:
break;
}
close(server_sd);
SOCKET(client_sd, info->domain, info->type, 0);
byte = 0;
if (write(client_sd, &byte, 1) != -1 || errno != ENOTCONN)
/* Yes, you fucked up the fix for the FIXME below. */
test_fail("write() should have yielded ENOTCONN");
if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1) {
if (!info->bug_connect_after_close) {
test_fail("connect() should have failed");
}
} else if (errno != ECONNRESET) {
test_fail("connect() should have yielded ECONNRESET");
}
/*
* FIXME: currently UDS cannot distinguish between sockets that have
* not yet been connected, and sockets that have been disconnected.
* Thus, we get the same error for both: ENOTCONN instead of EPIPE.
*/
#if 0
if (write(client_sd, &byte, 1) != -1 || errno != EPIPE)
test_fail("write() should have yielded EPIPE");
#endif
close(client_sd);
if (wait(&status) <= 0)
test_fail("wait() should have succeeded");
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
test_fail("child process failed the test");
info->callback_cleanup();
debug("leaving test_listen_close()");
}
/*
* Verify that closing a listening socket will cause a nonblocking connect to
* result in the socket becoming readable and writable, and yielding ECONNRESET
* and EPIPE on the next two writes, respectively.
*/
void
test_listen_close_nb(const struct socket_test_info *info)
{
int server_sd, client_sd;
int status;
char byte;
debug("entering test_listen_close_nb()");
SOCKET(server_sd, info->domain, info->type, 0);
if (bind(server_sd, info->serveraddr, info->serveraddrlen) == -1)
test_fail("bind() should have worked");
if (listen(server_sd, 8) == -1)
test_fail("listen() should have worked");
switch (fork()) {
case 0:
sleep(1);
exit(0);
case -1:
test_fail("can't fork");
default:
break;
}
close(server_sd);
SOCKET(client_sd, info->domain, info->type, 0);
fcntl(client_sd, F_SETFL, fcntl(client_sd, F_GETFL) | O_NONBLOCK);
if (connect(client_sd, info->clientaddr, info->clientaddrlen) != -1 ||
errno != EINPROGRESS)
test_fail("connect() should have yielded EINPROGRESS");
check_select_cond(client_sd, 0 /*read*/, 0 /*write*/, 0 /*block*/,
!info->ignore_select_delay);
check_select_cond(client_sd, 1 /*read*/, 1 /*write*/, 1 /*block*/,
!info->ignore_select_delay);
byte = 0;
if (write(client_sd, &byte, 1) != -1) {
if (!info->ignore_write_conn_reset) {
test_fail("write() should have failed");
}
} else if (errno != ECONNRESET) {
test_fail("write() should have yielded ECONNRESET");
}
/*
* FIXME: currently UDS cannot distinguish between sockets that have
* not yet been connected, and sockets that have been disconnected.
* Thus, we get the same error for both: ENOTCONN instead of EPIPE.
*/
#if 0
if (write(client_sd, &byte, 1) != -1 || errno != EPIPE)
test_fail("write() should have yielded EPIPE");
#endif
check_select_cond(client_sd, 1 /*read*/, 1 /*write*/, 0 /*block*/,
!info->ignore_select_delay);
close(client_sd);
if (wait(&status) <= 0)
test_fail("wait() should have succeeded");
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
test_fail("child process failed the test");
info->callback_cleanup();
debug("leaving test_listen_close_nb()");
}