mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-04-13 09:59:31 +00:00
selftests: mptcp: add disconnect tests
Performs several disconnect/reconnect on the same socket, ensuring the overall transfer is succesful. The new test leverages ioctl(SIOCOUTQ) to ensure all the pending data is acked before disconnecting. Additionally order alphabetically the test program arguments list for better maintainability. Signed-off-by: Paolo Abeni <pabeni@redhat.com> Signed-off-by: Mat Martineau <mathew.j.martineau@linux.intel.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
3d1d6d66e1
commit
05be5e273c
2 changed files with 160 additions and 27 deletions
|
@ -16,6 +16,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/poll.h>
|
#include <sys/poll.h>
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
|
|
||||||
#include <linux/tcp.h>
|
#include <linux/tcp.h>
|
||||||
#include <linux/time_types.h>
|
#include <linux/time_types.h>
|
||||||
|
#include <linux/sockios.h>
|
||||||
|
|
||||||
extern int optind;
|
extern int optind;
|
||||||
|
|
||||||
|
@ -68,6 +70,8 @@ static unsigned int cfg_time;
|
||||||
static unsigned int cfg_do_w;
|
static unsigned int cfg_do_w;
|
||||||
static int cfg_wait;
|
static int cfg_wait;
|
||||||
static uint32_t cfg_mark;
|
static uint32_t cfg_mark;
|
||||||
|
static char *cfg_input;
|
||||||
|
static int cfg_repeat = 1;
|
||||||
|
|
||||||
struct cfg_cmsg_types {
|
struct cfg_cmsg_types {
|
||||||
unsigned int cmsg_enabled:1;
|
unsigned int cmsg_enabled:1;
|
||||||
|
@ -91,22 +95,31 @@ static struct cfg_sockopt_types cfg_sockopt_types;
|
||||||
|
|
||||||
static void die_usage(void)
|
static void die_usage(void)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Usage: mptcp_connect [-6] [-u] [-s MPTCP|TCP] [-p port] [-m mode]"
|
fprintf(stderr, "Usage: mptcp_connect [-6] [-c cmsg] [-i file] [-I num] [-j] [-l] "
|
||||||
"[-l] [-w sec] [-t num] [-T num] connect_address\n");
|
"[-m mode] [-M mark] [-o option] [-p port] [-P mode] [-j] [-l] [-r num] "
|
||||||
|
"[-s MPTCP|TCP] [-S num] [-r num] [-t num] [-T num] [-u] [-w sec] connect_address\n");
|
||||||
fprintf(stderr, "\t-6 use ipv6\n");
|
fprintf(stderr, "\t-6 use ipv6\n");
|
||||||
fprintf(stderr, "\t-t num -- set poll timeout to num\n");
|
fprintf(stderr, "\t-c cmsg -- test cmsg type <cmsg>\n");
|
||||||
fprintf(stderr, "\t-T num -- set expected runtime to num ms\n");
|
fprintf(stderr, "\t-i file -- read the data to send from the given file instead of stdin");
|
||||||
fprintf(stderr, "\t-S num -- set SO_SNDBUF to num\n");
|
fprintf(stderr, "\t-I num -- repeat the transfer 'num' times. In listen mode accepts num "
|
||||||
fprintf(stderr, "\t-R num -- set SO_RCVBUF to num\n");
|
"incoming connections, in client mode, disconnect and reconnect to the server\n");
|
||||||
fprintf(stderr, "\t-p num -- use port num\n");
|
fprintf(stderr, "\t-j -- add additional sleep at connection start and tear down "
|
||||||
fprintf(stderr, "\t-s [MPTCP|TCP] -- use mptcp(default) or tcp sockets\n");
|
"-- for MPJ tests\n");
|
||||||
|
fprintf(stderr, "\t-l -- listens mode, accepts incoming connection\n");
|
||||||
fprintf(stderr, "\t-m [poll|mmap|sendfile] -- use poll(default)/mmap+write/sendfile\n");
|
fprintf(stderr, "\t-m [poll|mmap|sendfile] -- use poll(default)/mmap+write/sendfile\n");
|
||||||
fprintf(stderr, "\t-M mark -- set socket packet mark\n");
|
fprintf(stderr, "\t-M mark -- set socket packet mark\n");
|
||||||
fprintf(stderr, "\t-w num -- wait num sec before closing the socket\n");
|
|
||||||
fprintf(stderr, "\t-c cmsg -- test cmsg type <cmsg>\n");
|
|
||||||
fprintf(stderr, "\t-o option -- test sockopt <option>\n");
|
fprintf(stderr, "\t-o option -- test sockopt <option>\n");
|
||||||
|
fprintf(stderr, "\t-p num -- use port num\n");
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"\t-P [saveWithPeek|saveAfterPeek] -- save data with/after MSG_PEEK form tcp socket\n");
|
"\t-P [saveWithPeek|saveAfterPeek] -- save data with/after MSG_PEEK form tcp socket\n");
|
||||||
|
fprintf(stderr, "\t-t num -- set poll timeout to num\n");
|
||||||
|
fprintf(stderr, "\t-T num -- set expected runtime to num ms\n");
|
||||||
|
fprintf(stderr, "\t-r num -- enable slow mode, limiting each write to num bytes "
|
||||||
|
"-- for remove addr tests\n");
|
||||||
|
fprintf(stderr, "\t-R num -- set SO_RCVBUF to num\n");
|
||||||
|
fprintf(stderr, "\t-s [MPTCP|TCP] -- use mptcp(default) or tcp sockets\n");
|
||||||
|
fprintf(stderr, "\t-S num -- set SO_SNDBUF to num\n");
|
||||||
|
fprintf(stderr, "\t-w num -- wait num sec before closing the socket\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +323,8 @@ static int sock_listen_mptcp(const char * const listenaddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sock_connect_mptcp(const char * const remoteaddr,
|
static int sock_connect_mptcp(const char * const remoteaddr,
|
||||||
const char * const port, int proto)
|
const char * const port, int proto,
|
||||||
|
struct addrinfo **peer)
|
||||||
{
|
{
|
||||||
struct addrinfo hints = {
|
struct addrinfo hints = {
|
||||||
.ai_protocol = IPPROTO_TCP,
|
.ai_protocol = IPPROTO_TCP,
|
||||||
|
@ -334,8 +348,10 @@ static int sock_connect_mptcp(const char * const remoteaddr,
|
||||||
if (cfg_mark)
|
if (cfg_mark)
|
||||||
set_mark(sock, cfg_mark);
|
set_mark(sock, cfg_mark);
|
||||||
|
|
||||||
if (connect(sock, a->ai_addr, a->ai_addrlen) == 0)
|
if (connect(sock, a->ai_addr, a->ai_addrlen) == 0) {
|
||||||
|
*peer = a;
|
||||||
break; /* success */
|
break; /* success */
|
||||||
|
}
|
||||||
|
|
||||||
perror("connect()");
|
perror("connect()");
|
||||||
close(sock);
|
close(sock);
|
||||||
|
@ -524,14 +540,17 @@ static ssize_t do_rnd_read(const int fd, char *buf, const size_t len)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_nonblock(int fd)
|
static void set_nonblock(int fd, bool nonblock)
|
||||||
{
|
{
|
||||||
int flags = fcntl(fd, F_GETFL);
|
int flags = fcntl(fd, F_GETFL);
|
||||||
|
|
||||||
if (flags == -1)
|
if (flags == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
if (nonblock)
|
||||||
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
else
|
||||||
|
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int copyfd_io_poll(int infd, int peerfd, int outfd, bool *in_closed_after_out)
|
static int copyfd_io_poll(int infd, int peerfd, int outfd, bool *in_closed_after_out)
|
||||||
|
@ -543,7 +562,7 @@ static int copyfd_io_poll(int infd, int peerfd, int outfd, bool *in_closed_after
|
||||||
unsigned int woff = 0, wlen = 0;
|
unsigned int woff = 0, wlen = 0;
|
||||||
char wbuf[8192];
|
char wbuf[8192];
|
||||||
|
|
||||||
set_nonblock(peerfd);
|
set_nonblock(peerfd, true);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
char rbuf[8192];
|
char rbuf[8192];
|
||||||
|
@ -638,7 +657,6 @@ static int copyfd_io_poll(int infd, int peerfd, int outfd, bool *in_closed_after
|
||||||
if (cfg_remove)
|
if (cfg_remove)
|
||||||
usleep(cfg_wait);
|
usleep(cfg_wait);
|
||||||
|
|
||||||
close(peerfd);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -780,7 +798,7 @@ static int copyfd_io_sendfile(int infd, int peerfd, int outfd,
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int copyfd_io(int infd, int peerfd, int outfd)
|
static int copyfd_io(int infd, int peerfd, int outfd, bool close_peerfd)
|
||||||
{
|
{
|
||||||
bool in_closed_after_out = false;
|
bool in_closed_after_out = false;
|
||||||
struct timespec start, end;
|
struct timespec start, end;
|
||||||
|
@ -819,6 +837,9 @@ static int copyfd_io(int infd, int peerfd, int outfd)
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
if (close_peerfd)
|
||||||
|
close(peerfd);
|
||||||
|
|
||||||
if (cfg_time) {
|
if (cfg_time) {
|
||||||
unsigned int delta_ms;
|
unsigned int delta_ms;
|
||||||
|
|
||||||
|
@ -930,7 +951,7 @@ static void maybe_close(int fd)
|
||||||
{
|
{
|
||||||
unsigned int r = rand();
|
unsigned int r = rand();
|
||||||
|
|
||||||
if (!(cfg_join || cfg_remove) && (r & 1))
|
if (!(cfg_join || cfg_remove || cfg_repeat > 1) && (r & 1))
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -940,7 +961,9 @@ int main_loop_s(int listensock)
|
||||||
struct pollfd polls;
|
struct pollfd polls;
|
||||||
socklen_t salen;
|
socklen_t salen;
|
||||||
int remotesock;
|
int remotesock;
|
||||||
|
int fd = 0;
|
||||||
|
|
||||||
|
again:
|
||||||
polls.fd = listensock;
|
polls.fd = listensock;
|
||||||
polls.events = POLLIN;
|
polls.events = POLLIN;
|
||||||
|
|
||||||
|
@ -961,14 +984,27 @@ int main_loop_s(int listensock)
|
||||||
check_sockaddr(pf, &ss, salen);
|
check_sockaddr(pf, &ss, salen);
|
||||||
check_getpeername(remotesock, &ss, salen);
|
check_getpeername(remotesock, &ss, salen);
|
||||||
|
|
||||||
|
if (cfg_input) {
|
||||||
|
fd = open(cfg_input, O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
xerror("can't open %s: %d", cfg_input, errno);
|
||||||
|
}
|
||||||
|
|
||||||
SOCK_TEST_TCPULP(remotesock, 0);
|
SOCK_TEST_TCPULP(remotesock, 0);
|
||||||
|
|
||||||
return copyfd_io(0, remotesock, 1);
|
copyfd_io(fd, remotesock, 1, true);
|
||||||
|
} else {
|
||||||
|
perror("accept");
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
perror("accept");
|
if (--cfg_repeat > 0) {
|
||||||
|
if (cfg_input)
|
||||||
|
close(fd);
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init_rng(void)
|
static void init_rng(void)
|
||||||
|
@ -1057,15 +1093,47 @@ static void parse_setsock_options(const char *name)
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void xdisconnect(int fd, int addrlen)
|
||||||
|
{
|
||||||
|
struct sockaddr_storage empty;
|
||||||
|
int msec_sleep = 10;
|
||||||
|
int queued = 1;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
shutdown(fd, SHUT_WR);
|
||||||
|
|
||||||
|
/* while until the pending data is completely flushed, the later
|
||||||
|
* disconnect will bypass/ignore/drop any pending data.
|
||||||
|
*/
|
||||||
|
for (i = 0; ; i += msec_sleep) {
|
||||||
|
if (ioctl(fd, SIOCOUTQ, &queued) < 0)
|
||||||
|
xerror("can't query out socket queue: %d", errno);
|
||||||
|
|
||||||
|
if (!queued)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (i > poll_timeout)
|
||||||
|
xerror("timeout while waiting for spool to complete");
|
||||||
|
usleep(msec_sleep * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&empty, 0, sizeof(empty));
|
||||||
|
empty.ss_family = AF_UNSPEC;
|
||||||
|
if (connect(fd, (struct sockaddr *)&empty, addrlen) < 0)
|
||||||
|
xerror("can't disconnect: %d", errno);
|
||||||
|
}
|
||||||
|
|
||||||
int main_loop(void)
|
int main_loop(void)
|
||||||
{
|
{
|
||||||
int fd;
|
int fd, ret, fd_in = 0;
|
||||||
|
struct addrinfo *peer;
|
||||||
|
|
||||||
/* listener is ready. */
|
/* listener is ready. */
|
||||||
fd = sock_connect_mptcp(cfg_host, cfg_port, cfg_sock_proto);
|
fd = sock_connect_mptcp(cfg_host, cfg_port, cfg_sock_proto, &peer);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
return 2;
|
return 2;
|
||||||
|
|
||||||
|
again:
|
||||||
check_getpeername_connect(fd);
|
check_getpeername_connect(fd);
|
||||||
|
|
||||||
SOCK_TEST_TCPULP(fd, cfg_sock_proto);
|
SOCK_TEST_TCPULP(fd, cfg_sock_proto);
|
||||||
|
@ -1077,7 +1145,31 @@ int main_loop(void)
|
||||||
if (cfg_cmsg_types.cmsg_enabled)
|
if (cfg_cmsg_types.cmsg_enabled)
|
||||||
apply_cmsg_types(fd, &cfg_cmsg_types);
|
apply_cmsg_types(fd, &cfg_cmsg_types);
|
||||||
|
|
||||||
return copyfd_io(0, fd, 1);
|
if (cfg_input) {
|
||||||
|
fd_in = open(cfg_input, O_RDONLY);
|
||||||
|
if (fd < 0)
|
||||||
|
xerror("can't open %s:%d", cfg_input, errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* close the client socket open only if we are not going to reconnect */
|
||||||
|
ret = copyfd_io(fd_in, fd, 1, cfg_repeat == 1);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (--cfg_repeat > 0) {
|
||||||
|
xdisconnect(fd, peer->ai_addrlen);
|
||||||
|
|
||||||
|
/* the socket could be unblocking at this point, we need the
|
||||||
|
* connect to be blocking
|
||||||
|
*/
|
||||||
|
set_nonblock(fd, false);
|
||||||
|
if (connect(fd, peer->ai_addr, peer->ai_addrlen))
|
||||||
|
xerror("can't reconnect: %d", errno);
|
||||||
|
if (cfg_input)
|
||||||
|
close(fd_in);
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int parse_proto(const char *proto)
|
int parse_proto(const char *proto)
|
||||||
|
@ -1162,7 +1254,7 @@ static void parse_opts(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
while ((c = getopt(argc, argv, "6jr:lp:s:ht:T:m:S:R:w:M:P:c:o:")) != -1) {
|
while ((c = getopt(argc, argv, "6c:hi:I:jlm:M:o:p:P:r:R:s:S:t:T:w:")) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'j':
|
case 'j':
|
||||||
cfg_join = true;
|
cfg_join = true;
|
||||||
|
@ -1176,6 +1268,12 @@ static void parse_opts(int argc, char **argv)
|
||||||
if (cfg_do_w <= 0)
|
if (cfg_do_w <= 0)
|
||||||
cfg_do_w = 50;
|
cfg_do_w = 50;
|
||||||
break;
|
break;
|
||||||
|
case 'i':
|
||||||
|
cfg_input = optarg;
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
cfg_repeat = atoi(optarg);
|
||||||
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
listen_mode = true;
|
listen_mode = true;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -7,6 +7,7 @@ optstring="S:R:d:e:l:r:h4cm:f:tC"
|
||||||
ret=0
|
ret=0
|
||||||
sin=""
|
sin=""
|
||||||
sout=""
|
sout=""
|
||||||
|
cin_disconnect=""
|
||||||
cin=""
|
cin=""
|
||||||
cout=""
|
cout=""
|
||||||
ksft_skip=4
|
ksft_skip=4
|
||||||
|
@ -24,6 +25,7 @@ options_log=true
|
||||||
do_tcp=0
|
do_tcp=0
|
||||||
checksum=false
|
checksum=false
|
||||||
filesize=0
|
filesize=0
|
||||||
|
connect_per_transfer=1
|
||||||
|
|
||||||
if [ $tc_loss -eq 100 ];then
|
if [ $tc_loss -eq 100 ];then
|
||||||
tc_loss=1%
|
tc_loss=1%
|
||||||
|
@ -127,6 +129,7 @@ TEST_COUNT=0
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
{
|
{
|
||||||
|
rm -f "$cin_disconnect" "$cout_disconnect"
|
||||||
rm -f "$cin" "$cout"
|
rm -f "$cin" "$cout"
|
||||||
rm -f "$sin" "$sout"
|
rm -f "$sin" "$sout"
|
||||||
rm -f "$capout"
|
rm -f "$capout"
|
||||||
|
@ -149,6 +152,8 @@ sout=$(mktemp)
|
||||||
cin=$(mktemp)
|
cin=$(mktemp)
|
||||||
cout=$(mktemp)
|
cout=$(mktemp)
|
||||||
capout=$(mktemp)
|
capout=$(mktemp)
|
||||||
|
cin_disconnect="$cin".disconnect
|
||||||
|
cout_disconnect="$cout".disconnect
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
for i in "$ns1" "$ns2" "$ns3" "$ns4";do
|
for i in "$ns1" "$ns2" "$ns3" "$ns4";do
|
||||||
|
@ -500,8 +505,8 @@ do_transfer()
|
||||||
cookies=${cookies##*=}
|
cookies=${cookies##*=}
|
||||||
|
|
||||||
if [ ${cl_proto} = "MPTCP" ] && [ ${srv_proto} = "MPTCP" ]; then
|
if [ ${cl_proto} = "MPTCP" ] && [ ${srv_proto} = "MPTCP" ]; then
|
||||||
expect_synrx=$((stat_synrx_last_l+1))
|
expect_synrx=$((stat_synrx_last_l+$connect_per_transfer))
|
||||||
expect_ackrx=$((stat_ackrx_last_l+1))
|
expect_ackrx=$((stat_ackrx_last_l+$connect_per_transfer))
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ${stat_synrx_now_l} -lt ${expect_synrx} ]; then
|
if [ ${stat_synrx_now_l} -lt ${expect_synrx} ]; then
|
||||||
|
@ -738,6 +743,33 @@ run_tests_peekmode()
|
||||||
run_tests_lo "$ns1" "$ns1" dead:beef:1::1 1 "-P ${peekmode}"
|
run_tests_lo "$ns1" "$ns1" dead:beef:1::1 1 "-P ${peekmode}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_tests_disconnect()
|
||||||
|
{
|
||||||
|
local peekmode="$1"
|
||||||
|
local old_cin=$cin
|
||||||
|
local old_sin=$sin
|
||||||
|
|
||||||
|
cat $cin $cin $cin > "$cin".disconnect
|
||||||
|
|
||||||
|
# force do_transfer to cope with the multiple tranmissions
|
||||||
|
sin="$cin.disconnect"
|
||||||
|
sin_disconnect=$old_sin
|
||||||
|
cin="$cin.disconnect"
|
||||||
|
cin_disconnect="$old_cin"
|
||||||
|
connect_per_transfer=3
|
||||||
|
|
||||||
|
echo "INFO: disconnect"
|
||||||
|
run_tests_lo "$ns1" "$ns1" 10.0.1.1 1 "-I 3 -i $old_cin"
|
||||||
|
run_tests_lo "$ns1" "$ns1" dead:beef:1::1 1 "-I 3 -i $old_cin"
|
||||||
|
|
||||||
|
# restore previous status
|
||||||
|
cout=$old_cout
|
||||||
|
cout_disconnect="$cout".disconnect
|
||||||
|
cin=$old_cin
|
||||||
|
cin_disconnect="$cin".disconnect
|
||||||
|
connect_per_transfer=1
|
||||||
|
}
|
||||||
|
|
||||||
display_time()
|
display_time()
|
||||||
{
|
{
|
||||||
time_end=$(date +%s)
|
time_end=$(date +%s)
|
||||||
|
@ -853,6 +885,9 @@ stop_if_error "Tests with peek mode have failed"
|
||||||
# connect to ns4 ip address, ns2 should intercept/proxy
|
# connect to ns4 ip address, ns2 should intercept/proxy
|
||||||
run_test_transparent 10.0.3.1 "tproxy ipv4"
|
run_test_transparent 10.0.3.1 "tproxy ipv4"
|
||||||
run_test_transparent dead:beef:3::1 "tproxy ipv6"
|
run_test_transparent dead:beef:3::1 "tproxy ipv6"
|
||||||
|
stop_if_error "Tests with tproxy have failed"
|
||||||
|
|
||||||
|
run_tests_disconnect
|
||||||
|
|
||||||
display_time
|
display_time
|
||||||
exit $ret
|
exit $ret
|
||||||
|
|
Loading…
Add table
Reference in a new issue