1
0
mirror of https://github.com/samba-team/samba.git synced 2025-02-09 09:57:48 +03:00

lib/tsocket: Add tests for loop on EAGAIN

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15202

Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
(cherry picked from commit f0fb8b9508346aed50528216fd959a9b1a941409)
This commit is contained in:
Andrew Bartlett 2022-10-17 16:08:42 +13:00 committed by Jule Anger
parent 0b56228573
commit dcac415e94
6 changed files with 650 additions and 0 deletions

View File

@ -0,0 +1,89 @@
/*
Unix SMB/CIFS implementation.
Samba utility functions
Copyright (C) Andrew Tridgell 1992-1998
Copyright (C) Tim Potter 2000-2001
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "includes.h"
#include "system/network.h"
#include "socketpair_tcp.h"
/*******************************************************************
this is like socketpair but uses tcp. It is used by the Samba
regression test code
The function guarantees that nobody else can attach to the socket,
or if they do that this function fails and the socket gets closed
returns 0 on success, -1 on failure
the resulting file descriptors are symmetrical
******************************************************************/
int socketpair_tcp(int fd[2])
{
int listener;
struct sockaddr_in sock;
struct sockaddr_in sock2;
socklen_t socklen = sizeof(sock);
int connect_done = 0;
fd[0] = fd[1] = listener = -1;
memset(&sock, 0, sizeof(sock));
if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1) goto failed;
memset(&sock2, 0, sizeof(sock2));
#ifdef HAVE_SOCK_SIN_LEN
sock2.sin_len = sizeof(sock2);
#endif
sock2.sin_family = PF_INET;
if (bind(listener, (struct sockaddr *)&sock2, sizeof(sock2)) != 0) goto failed;
if (listen(listener, 1) != 0) goto failed;
if (getsockname(listener, (struct sockaddr *)&sock, &socklen) != 0) goto failed;
if ((fd[1] = socket(PF_INET, SOCK_STREAM, 0)) == -1) goto failed;
set_blocking(fd[1], 0);
sock.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (connect(fd[1], (struct sockaddr *)&sock, socklen) == -1) {
if (errno != EINPROGRESS) goto failed;
} else {
connect_done = 1;
}
if ((fd[0] = accept(listener, (struct sockaddr *)&sock, &socklen)) == -1) goto failed;
if (connect_done == 0) {
if (connect(fd[1], (struct sockaddr *)&sock, socklen) != 0
&& errno != EISCONN) goto failed;
}
close(listener);
set_blocking(fd[1], 1);
/* all OK! */
return 0;
failed:
if (fd[0] != -1) close(fd[0]);
if (fd[1] != -1) close(fd[1]);
if (listener != -1) close(listener);
return -1;
}

View File

@ -0,0 +1,29 @@
/*
Unix SMB/CIFS implementation.
Samba utility functions
Copyright (C) Andrew Tridgell 1992-1998
Copyright (C) Tim Potter 2000-2001
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*******************************************************************
this is like socketpair but uses tcp. It is used by the Samba
regression test code
The function guarantees that nobody else can attach to the socket,
or if they do that this function fails and the socket gets closed
returns 0 on success, -1 on failure
the resulting file descriptors are symmetrical
******************************************************************/
int socketpair_tcp(int fd[2]);

View File

@ -0,0 +1,517 @@
/*
* Unix SMB/CIFS implementation.
*
* Copyright (C) 2022 Andrew Bartlett <abartlet@samba.org>
* Copyright (C) 2021 Andreas Schneider <asn@samba.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <setjmp.h>
#include <cmocka.h>
#include "includes.h"
#include "system/network.h"
#include "socketpair_tcp.h"
#include "tsocket.h"
enum socket_pair_selector {
SOCKET_SERVER = 0,
SOCKET_CLIENT = 1,
};
struct socket_pair {
struct tevent_context *ev;
int socket_server;
int socket_client;
/* for tstream tests */
int rc;
int sys_errno;
int expected_errno;
struct timeval endtime;
size_t max_loops;
size_t num_loops;
};
/* If this is too large, we get EPIPE rather than EAGAIN */
static const uint8_t TEST_STRING[128] = { 0 };
static int sigpipe_setup(void **state)
{
BlockSignals(true, SIGPIPE);
return 0;
}
static int setup_socketpair_tcp_context(void **state)
{
int fd[2];
struct socket_pair *sp = talloc_zero(NULL, struct socket_pair);
assert_non_null(sp);
/* Set up a socketpair over TCP to test with */
assert_return_code(socketpair_tcp(fd), errno);
sp->socket_server = fd[SOCKET_SERVER];
sp->socket_client = fd[SOCKET_CLIENT];
sp->ev = tevent_context_init(sp);
assert_non_null(sp->ev);
*state = sp;
return 0;
}
static int setup_socketpair_context(void **state)
{
int fd[2];
struct socket_pair *sp = talloc_zero(NULL, struct socket_pair);
assert_non_null(sp);
/* Set up a socketpair over TCP to test with */
assert_return_code(socketpair(AF_UNIX, SOCK_STREAM, 0, fd), errno);
sp->socket_server = fd[SOCKET_SERVER];
sp->socket_client = fd[SOCKET_CLIENT];
sp->ev = tevent_context_init(sp);
assert_non_null(sp->ev);
*state = sp;
return 0;
}
static int teardown_socketpair_context(void **state)
{
struct socket_pair *sp = *state;
struct socket_pair sp_save = *sp;
TALLOC_FREE(sp);
/*
* Close these after the TALLOC_FREE() to allow clean shutdown
* of epoll() in tstream
*/
if (sp_save.socket_client != -1) {
close(sp_save.socket_client);
}
if (sp_save.socket_server != -1) {
close(sp_save.socket_server);
}
return 0;
}
/* Test socket behaviour */
static void test_simple_socketpair(void **state) {
struct socket_pair *sp = *state;
char buf[sizeof(TEST_STRING)];
assert_int_equal(write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING)),
sizeof(TEST_STRING));
assert_int_equal(read(sp->socket_client, buf, sizeof(buf)),
sizeof(buf));
}
/* Test socket behaviour */
static void test_read_client_after_close_server_socket(void **state) {
struct socket_pair *sp = *state;
int rc;
char buf[sizeof(TEST_STRING)];
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
assert_int_equal(rc, sizeof(TEST_STRING));
assert_return_code(close(sp->socket_server), 0);
rc = read(sp->socket_client, buf, sizeof(buf));
assert_return_code(rc, errno);
assert_int_equal(rc, sizeof(buf));
}
static void test_write_server_after_close_client_socket(void **state) {
struct socket_pair *sp = *state;
int rc;
assert_return_code(close(sp->socket_client), 0);
sp->socket_client = -1;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
assert_int_equal(rc, sizeof(TEST_STRING));
}
static void test_fill_socket(int sock)
{
size_t num_busy = 0;
int rc;
while (true) {
rc = write(sock, TEST_STRING, sizeof(TEST_STRING));
if (rc == -1 && errno == EAGAIN) {
/*
* This makes sure we write until we get a whole second
* only with EAGAIN every 50 ms (20 times)
*
* Otherwise the tests are not reliable...
*/
num_busy++;
if (num_busy > 20) {
break;
}
smb_msleep(50);
continue;
}
/* try again next time */
num_busy = 0;
}
assert_int_equal(rc, -1);
assert_int_equal(errno, EAGAIN);
}
static void test_big_write_server(void **state) {
struct socket_pair *sp = *state;
int rc;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
assert_int_equal(rc, sizeof(TEST_STRING));
rc = set_blocking(sp->socket_server, 0);
assert_return_code(rc, errno);
test_fill_socket(sp->socket_server);
}
static void test_big_write_server_close_write(void **state) {
struct socket_pair *sp = *state;
int rc;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
assert_int_equal(rc, sizeof(TEST_STRING));
rc = set_blocking(sp->socket_server, 0);
assert_return_code(rc, errno);
test_fill_socket(sp->socket_server);
assert_return_code(close(sp->socket_client), 0);
sp->socket_client = -1;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_int_equal(errno, ECONNRESET);
}
static void test_big_write_server_shutdown_wr_write(void **state) {
struct socket_pair *sp = *state;
int rc;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
assert_int_equal(rc, sizeof(TEST_STRING));
rc = set_blocking(sp->socket_server, 0);
assert_return_code(rc, errno);
test_fill_socket(sp->socket_server);
assert_return_code(shutdown(sp->socket_client, SHUT_WR), 0);
sp->socket_client = -1;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_int_equal(rc, -1);
assert_int_equal(errno, EAGAIN);
}
static void test_big_write_server_shutdown_rd_write(void **state) {
struct socket_pair *sp = *state;
int rc;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
assert_int_equal(rc, sizeof(TEST_STRING));
rc = set_blocking(sp->socket_server, 0);
assert_return_code(rc, errno);
test_fill_socket(sp->socket_server);
assert_return_code(shutdown(sp->socket_client, SHUT_RD), 0);
sp->socket_client = -1;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_int_equal(rc, -1);
assert_int_equal(errno, EAGAIN);
}
static void test_call_writev_done(struct tevent_req *subreq)
{
struct socket_pair *sp =
tevent_req_callback_data(subreq,
struct socket_pair);
int rc;
rc = tstream_writev_recv(subreq, &sp->sys_errno);
TALLOC_FREE(subreq);
sp->rc = rc;
}
static void test_tstream_server_spin_client_shutdown(struct socket_pair *sp)
{
int rc;
rc = shutdown(sp->socket_client, SHUT_WR);
assert_return_code(rc, errno);
/*
* It should only take a few additional loop to realise that this socket is
* in CLOSE_WAIT
*/
sp->max_loops = sp->num_loops + 2;
sp->expected_errno = ECONNRESET;
}
static void test_tstream_server_spin_client_write(struct socket_pair *sp)
{
int rc;
int timeout = 5000;
sp->endtime = timeval_current_ofs_msec(timeout);
rc = write(sp->socket_client, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
sp->expected_errno = ETIMEDOUT;
}
static void test_tstream_server_spin_client_tcp_user_timeout(struct socket_pair *sp)
{
int rc;
int timeout = 5000;
rc = setsockopt(sp->socket_server, IPPROTO_TCP, TCP_USER_TIMEOUT, &timeout, sizeof(timeout));
assert_return_code(rc, errno);
rc = write(sp->socket_client, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
sp->expected_errno = ETIMEDOUT;
sp->max_loops = 15;
}
static void test_tstream_server_spin_client_both_timer(struct tevent_context *ev,
struct tevent_timer *te,
struct timeval current_time,
void *private_data)
{
struct socket_pair *sp =
talloc_get_type_abort(private_data,
struct socket_pair);
test_tstream_server_spin_client_shutdown(sp);
}
static void test_tstream_server_spin_client_both(struct socket_pair *sp)
{
struct tevent_timer *te = NULL;
struct timeval endtime;
test_tstream_server_spin_client_write(sp);
endtime = timeval_current_ofs_msec(2500);
te = tevent_add_timer(sp->ev,
sp,
endtime,
test_tstream_server_spin_client_both_timer,
sp);
assert_non_null(te);
sp->expected_errno = ENXIO;
}
static void test_tstream_server_spin(struct socket_pair *sp,
void (*client_fn)(struct socket_pair *sp))
{
struct tstream_context *stream = NULL;
struct tevent_req *req = NULL;
struct iovec iov;
int rc;
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_return_code(rc, errno);
assert_int_equal(rc, sizeof(TEST_STRING));
rc = set_blocking(sp->socket_server, 0);
assert_return_code(rc, errno);
test_fill_socket(sp->socket_server);
/*
* by default we don't expect more then 2 loop iterations
* for a timeout of 5 seconds.
*/
sp->max_loops = 10;
client_fn(sp);
rc = write(sp->socket_server, TEST_STRING, sizeof(TEST_STRING));
assert_int_equal(rc, -1);
assert_int_equal(errno, EAGAIN);
/* OK, so we now know the socket is in CLOSE_WAIT */
rc = tstream_bsd_existing_socket(sp->ev, sp->socket_server, &stream);
assert_return_code(rc, errno);
sp->socket_server = -1;
iov.iov_base = discard_const_p(char, TEST_STRING);
iov.iov_len = sizeof(TEST_STRING);
req = tstream_writev_send(stream, sp->ev, stream, &iov, 1);
assert_non_null(req);
if (!timeval_is_zero(&sp->endtime)) {
assert_true(tevent_req_set_endtime(req, sp->ev, sp->endtime));
}
tevent_req_set_callback(req, test_call_writev_done, sp);
while (tevent_req_is_in_progress(req)) {
if (sp->num_loops >= sp->max_loops) {
assert_int_not_equal(sp->num_loops, sp->max_loops);
assert_int_equal(sp->num_loops, sp->max_loops);
}
sp->num_loops += 1;
rc = tevent_loop_once(sp->ev);
assert_int_equal(rc, 0);
}
assert_int_equal(sp->rc, -1);
assert_int_equal(sp->sys_errno, sp->expected_errno);
return;
}
/*
* We need two names to run this with the two different setup
* routines
*/
static void test_tstream_disconnected_tcp_client_spin(void **state)
{
struct socket_pair *sp = *state;
test_tstream_server_spin(sp, test_tstream_server_spin_client_shutdown);
}
static void test_tstream_disconnected_unix_client_spin(void **state)
{
struct socket_pair *sp = *state;
test_tstream_server_spin(sp, test_tstream_server_spin_client_shutdown);
}
static void test_tstream_more_tcp_client_spin(void **state)
{
struct socket_pair *sp = *state;
test_tstream_server_spin(sp, test_tstream_server_spin_client_write);
}
static void test_tstream_more_unix_client_spin(void **state)
{
struct socket_pair *sp = *state;
test_tstream_server_spin(sp, test_tstream_server_spin_client_write);
}
static void test_tstream_more_disconnect_tcp_client_spin(void **state)
{
struct socket_pair *sp = *state;
test_tstream_server_spin(sp, test_tstream_server_spin_client_both);
}
static void test_tstream_more_disconnect_unix_client_spin(void **state)
{
struct socket_pair *sp = *state;
test_tstream_server_spin(sp, test_tstream_server_spin_client_both);
}
static void test_tstream_more_tcp_user_timeout_spin(void **state)
{
struct socket_pair *sp = *state;
if (socket_wrapper_enabled()) {
skip();
}
test_tstream_server_spin(sp, test_tstream_server_spin_client_tcp_user_timeout);
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_simple_socketpair,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_read_client_after_close_server_socket,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_write_server_after_close_client_socket,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_big_write_server,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_big_write_server_close_write,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_big_write_server_shutdown_wr_write,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_big_write_server_shutdown_rd_write,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_tstream_disconnected_tcp_client_spin,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_tstream_disconnected_unix_client_spin,
setup_socketpair_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_tstream_more_tcp_client_spin,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_tstream_more_unix_client_spin,
setup_socketpair_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_tstream_more_disconnect_tcp_client_spin,
setup_socketpair_tcp_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_tstream_more_disconnect_unix_client_spin,
setup_socketpair_context,
teardown_socketpair_context),
cmocka_unit_test_setup_teardown(test_tstream_more_tcp_user_timeout_spin,
setup_socketpair_tcp_context,
teardown_socketpair_context),
};
cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
return cmocka_run_group_tests(tests, sigpipe_setup, NULL);
}

View File

@ -13,3 +13,9 @@ bld.SAMBA_BINARY('test_tsocket_bsd_addr',
local_include=False,
for_selftest=True)
bld.SAMBA_BINARY('test_tstream',
source='tests/test_tstream.c tests/socketpair_tcp.c',
deps='cmocka replace LIBTSOCKET',
local_include=False,
for_selftest=True)

View File

@ -0,0 +1,6 @@
^samba.unittests.tsocket_tstream.test_tstream_disconnected_tcp_client_spin
^samba.unittests.tsocket_tstream.test_tstream_disconnected_unix_client_spin
^samba.unittests.tsocket_tstream.test_tstream_more_tcp_client_spin
^samba.unittests.tsocket_tstream.test_tstream_more_unix_client_spin
^samba.unittests.tsocket_tstream.test_tstream_more_disconnect_tcp_client_spin
^samba.unittests.tsocket_tstream.test_tstream_more_disconnect_unix_client_spin

View File

@ -466,6 +466,9 @@ plantestsuite("samba.unittests.credentials", "none",
[os.path.join(bindir(), "default/auth/credentials/test_creds")])
plantestsuite("samba.unittests.tsocket_bsd_addr", "none",
[os.path.join(bindir(), "default/lib/tsocket/test_tsocket_bsd_addr")])
plantestsuite("samba.unittests.tsocket_tstream", "none",
[os.path.join(bindir(), "default/lib/tsocket/test_tstream")],
environ={'SOCKET_WRAPPER_DIR': ''})
plantestsuite("samba.unittests.adouble", "none",
[os.path.join(bindir(), "test_adouble")])
plantestsuite("samba.unittests.gnutls_aead_aes_256_cbc_hmac_sha512", "none",