netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Mat Martineau <mathew.j.martineau@linux.intel.com>
To: edumazet@google.com, netdev@vger.kernel.org
Cc: Florian Westphal <fw@strlen.de>,
	cpaasch@apple.com, pabeni@redhat.com,
	peter.krystad@linux.intel.com, dcaratti@redhat.com,
	matthieu.baerts@tessares.net
Subject: [RFC PATCH net-next 22/33] mptcp: add basic kselftest program
Date: Mon, 17 Jun 2019 15:57:57 -0700	[thread overview]
Message-ID: <20190617225808.665-23-mathew.j.martineau@linux.intel.com> (raw)
In-Reply-To: <20190617225808.665-1-mathew.j.martineau@linux.intel.com>

From: Florian Westphal <fw@strlen.de>

create mptcp connection between two processes, xmit data back and
forth.  Data is read from stdin and written (after traversing mtcp
connection twice) to stdout.

Wrapper script tests that data has passed un-altered.

Will run automatically on "make kselftest".

Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
 tools/testing/selftests/Makefile              |   1 +
 tools/testing/selftests/net/mptcp/.gitignore  |   1 +
 tools/testing/selftests/net/mptcp/Makefile    |  11 +
 tools/testing/selftests/net/mptcp/config      |   1 +
 .../selftests/net/mptcp/mptcp_connect.c       | 390 ++++++++++++++++++
 .../selftests/net/mptcp/mptcp_connect.sh      |  48 +++
 6 files changed, 452 insertions(+)
 create mode 100644 tools/testing/selftests/net/mptcp/.gitignore
 create mode 100644 tools/testing/selftests/net/mptcp/Makefile
 create mode 100644 tools/testing/selftests/net/mptcp/config
 create mode 100644 tools/testing/selftests/net/mptcp/mptcp_connect.c
 create mode 100755 tools/testing/selftests/net/mptcp/mptcp_connect.sh

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 9781ca79794a..e949f1be6773 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -29,6 +29,7 @@ TARGETS += memory-hotplug
 TARGETS += mount
 TARGETS += mqueue
 TARGETS += net
+TARGETS += net/mptcp
 TARGETS += netfilter
 TARGETS += networking/timestamping
 TARGETS += nsfs
diff --git a/tools/testing/selftests/net/mptcp/.gitignore b/tools/testing/selftests/net/mptcp/.gitignore
new file mode 100644
index 000000000000..3143fb05a511
--- /dev/null
+++ b/tools/testing/selftests/net/mptcp/.gitignore
@@ -0,0 +1 @@
+mptcp_connect
diff --git a/tools/testing/selftests/net/mptcp/Makefile b/tools/testing/selftests/net/mptcp/Makefile
new file mode 100644
index 000000000000..0fc5d45055ee
--- /dev/null
+++ b/tools/testing/selftests/net/mptcp/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+
+top_srcdir = ../../../../..
+
+CFLAGS =  -Wall -Wl,--no-as-needed -O2 -g
+
+TEST_PROGS := mptcp_connect.sh
+
+TEST_GEN_FILES = mptcp_connect
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/net/mptcp/config b/tools/testing/selftests/net/mptcp/config
new file mode 100644
index 000000000000..3bfe60494af8
--- /dev/null
+++ b/tools/testing/selftests/net/mptcp/config
@@ -0,0 +1 @@
+CONFIG_MPTCP=y
diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.c b/tools/testing/selftests/net/mptcp/mptcp_connect.c
new file mode 100644
index 000000000000..78c43624e84f
--- /dev/null
+++ b/tools/testing/selftests/net/mptcp/mptcp_connect.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+extern int optind;
+
+#ifndef IPPROTO_MPTCP
+#define IPPROTO_MPTCP 262
+#endif
+
+static const char *cfg_host;
+static const char *cfg_port	= "12000";
+static int cfg_server_proto	= IPPROTO_MPTCP;
+static int cfg_client_proto	= IPPROTO_MPTCP;
+
+static void die_usage(void)
+{
+	fprintf(stderr, "Usage: mptcp_connect [-c MPTCP|TCP] [-p port] "
+		"[-s MPTCP|TCP]\n");
+	exit(-1);
+}
+
+static const char *getxinfo_strerr(int err)
+{
+	if (err == EAI_SYSTEM)
+		return strerror(errno);
+
+	return gai_strerror(err);
+}
+
+static void xgetaddrinfo(const char *node, const char *service,
+			 const struct addrinfo *hints,
+			 struct addrinfo **res)
+{
+	int err = getaddrinfo(node, service, hints, res);
+
+	if (err) {
+		const char *errstr = getxinfo_strerr(err);
+
+		fprintf(stderr, "Fatal: getaddrinfo(%s:%s): %s\n",
+			node ? node : "", service ? service : "", errstr);
+		exit(1);
+	}
+}
+
+static int sock_listen_mptcp(const char * const listenaddr,
+			     const char * const port)
+{
+	int sock;
+	struct addrinfo hints = {
+		.ai_protocol = IPPROTO_TCP,
+		.ai_socktype = SOCK_STREAM,
+		.ai_flags = AI_PASSIVE | AI_NUMERICHOST
+	};
+
+	hints.ai_family = AF_INET;
+
+	struct addrinfo *a, *addr;
+	int one = 1;
+
+	xgetaddrinfo(listenaddr, port, &hints, &addr);
+
+	for (a = addr; a; a = a->ai_next) {
+		sock = socket(a->ai_family, a->ai_socktype, cfg_server_proto);
+		if (sock < 0) {
+			perror("socket");
+			continue;
+		}
+
+		if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one,
+				     sizeof(one)))
+			perror("setsockopt");
+
+		if (bind(sock, a->ai_addr, a->ai_addrlen) == 0)
+			break; /* success */
+
+		perror("bind");
+		close(sock);
+		sock = -1;
+	}
+
+	if (sock >= 0 && listen(sock, 20))
+		perror("listen");
+
+	freeaddrinfo(addr);
+	return sock;
+}
+
+static int sock_connect_mptcp(const char * const remoteaddr,
+			      const char * const port, int proto)
+{
+	struct addrinfo hints = {
+		.ai_protocol = IPPROTO_TCP,
+		.ai_socktype = SOCK_STREAM,
+	};
+	struct addrinfo *a, *addr;
+	int sock = -1;
+
+	hints.ai_family = AF_INET;
+
+	xgetaddrinfo(remoteaddr, port, &hints, &addr);
+	for (a = addr; a; a = a->ai_next) {
+		sock = socket(a->ai_family, a->ai_socktype, proto);
+		if (sock < 0) {
+			perror("socket");
+			continue;
+		}
+
+		if (connect(sock, a->ai_addr, a->ai_addrlen) == 0)
+			break; /* success */
+
+		perror("connect()");
+		close(sock);
+		sock = -1;
+	}
+
+	freeaddrinfo(addr);
+	return sock;
+}
+
+static size_t do_write(const int fd, char *buf, const size_t len)
+{
+	size_t offset = 0;
+
+	while (offset < len) {
+		unsigned int do_w;
+		size_t written;
+		ssize_t bw;
+
+		do_w = rand() & 0xffff;
+		if (do_w == 0 || do_w > (len - offset))
+			do_w = len - offset;
+
+		bw = write(fd, buf + offset, do_w);
+		if (bw < 0) {
+			perror("write");
+			return 0;
+		}
+
+		written = (size_t)bw;
+		offset += written;
+	}
+	return offset;
+}
+
+static void copyfd_io(int peerfd)
+{
+	struct pollfd fds = { .events = POLLIN };
+
+	fds.fd = peerfd;
+
+	for (;;) {
+		char buf[4096];
+		ssize_t len;
+
+		switch (poll(&fds, 1, -1)) {
+		case -1:
+			if (errno == EINTR)
+				continue;
+			perror("poll");
+			return;
+		case 0:
+			/* should not happen, we requested infinite wait */
+			fputs("Timed out?!", stderr);
+			return;
+		}
+
+		if ((fds.revents & POLLIN) == 0)
+			return;
+
+		len = read(peerfd, buf, sizeof(buf));
+		if (!len)
+			return;
+		if (len < 0) {
+			if (errno == EINTR)
+				continue;
+
+			perror("read");
+			return;
+		}
+
+		if (!do_write(peerfd, buf, len))
+			return;
+	}
+}
+
+int main_loop_s(int listensock)
+{
+	struct sockaddr_storage ss;
+	socklen_t salen;
+	int remotesock;
+
+	salen = sizeof(ss);
+	while ((remotesock = accept(listensock, (struct sockaddr *)&ss,
+				    &salen)) < 0)
+		perror("accept");
+
+	copyfd_io(remotesock);
+	close(remotesock);
+
+	return 0;
+}
+
+static void init_rng(void)
+{
+	int fd = open("/dev/urandom", O_RDONLY);
+	unsigned int foo;
+
+	if (fd > 0) {
+		read(fd, &foo, sizeof(foo));
+		close(fd);
+	}
+
+	srand(foo);
+}
+
+int main_loop(void)
+{
+	int pollfds = 2, timeout = -1;
+	char start[32];
+	int pipefd[2];
+	ssize_t ret;
+	int fd;
+
+	if (pipe(pipefd)) {
+		perror("pipe");
+		exit(1);
+	}
+
+	switch (fork()) {
+	case 0:
+		close(pipefd[0]);
+
+		init_rng();
+
+		fd = sock_listen_mptcp(NULL, cfg_port);
+		if (fd < 0)
+			return -1;
+
+		write(pipefd[1], "RDY\n", 4);
+		main_loop_s(fd);
+		exit(1);
+	case -1:
+		perror("fork");
+		return -1;
+	default:
+		close(pipefd[1]);
+		break;
+	}
+
+	init_rng();
+	ret = read(pipefd[0], start, (int)sizeof(start));
+	if (ret < 0) {
+		perror("read");
+		return -1;
+	}
+
+	if (ret != 4 || strcmp(start, "RDY\n"))
+		return -1;
+
+	/* listener is ready. */
+	fd = sock_connect_mptcp(cfg_host, cfg_port, cfg_client_proto);
+	if (fd < 0)
+		return -1;
+
+	for (;;) {
+		struct pollfd fds[2];
+		char buf[4096];
+		ssize_t len;
+
+		fds[0].fd = fd;
+		fds[0].events = POLLIN;
+		fds[1].fd = 0;
+		fds[1].events = POLLIN;
+		fds[1].revents = 0;
+
+		switch (poll(fds, pollfds, timeout)) {
+		case -1:
+			if (errno == EINTR)
+				continue;
+			perror("poll");
+			return -1;
+		case 0:
+			close(fd);
+			return 0;
+		}
+
+		if (fds[0].revents & POLLIN) {
+			unsigned int blen = rand();
+
+			blen %= sizeof(buf);
+
+			++blen;
+			len = read(fd, buf, blen);
+			if (len < 0) {
+				perror("read");
+				return -1;
+			}
+
+			if (len > blen) {
+				fprintf(stderr, "read returned more data than "
+						"buffer length\n");
+				len = blen;
+			}
+
+			write(1, buf, len);
+		}
+		if (fds[1].revents & POLLIN) {
+			len = read(0, buf, sizeof(buf));
+			if (len == 0) {
+				pollfds = 1;
+				timeout = 1000;
+				continue;
+			}
+
+			if (len < 0) {
+				perror("read");
+				break;
+			}
+
+			do_write(fd, buf, len);
+		}
+	}
+
+	return 1;
+}
+
+int parse_proto(const char *proto)
+{
+	if (!strcasecmp(proto, "MPTCP"))
+		return IPPROTO_MPTCP;
+	if (!strcasecmp(proto, "TCP"))
+		return IPPROTO_TCP;
+	die_usage();
+
+	/* silence compiler warning */
+	return 0;
+}
+
+static void parse_opts(int argc, char **argv)
+{
+	int c;
+
+	while ((c = getopt(argc, argv, "c:p:s:h")) != -1) {
+		switch (c) {
+		case 'c':
+			cfg_client_proto = parse_proto(optarg);
+			break;
+		case 'p':
+			cfg_port = optarg;
+			break;
+		case 's':
+			cfg_server_proto = parse_proto(optarg);
+			break;
+		case 'h':
+			die_usage();
+			break;
+		}
+	}
+
+	if (optind + 1 != argc)
+		die_usage();
+	cfg_host = argv[optind];
+}
+
+int main(int argc, char *argv[])
+{
+	init_rng();
+
+	parse_opts(argc, argv);
+	return main_loop();
+}
diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.sh b/tools/testing/selftests/net/mptcp/mptcp_connect.sh
new file mode 100755
index 000000000000..efcdda84b62a
--- /dev/null
+++ b/tools/testing/selftests/net/mptcp/mptcp_connect.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+
+tmpin=$(mktemp)
+tmpout=$(mktemp)
+
+cleanup()
+{
+	rm -f "$tmpin" "$tmpout"
+}
+
+check_transfer()
+{
+	cl_proto=${1}
+	srv_proto=${2}
+
+	printf "%-8s -> %-8s socket\t\t" ${cl_proto} ${srv_proto}
+
+	./mptcp_connect -c ${cl_proto} -p 43212 -s ${srv_proto} 127.0.0.1  < "$tmpin" > "$tmpout" 2>/dev/null
+	ret=$?
+	if [ ${ret} -ne 0 ]; then
+		echo "[ FAIL ]"
+		echo " exit code ${ret}"
+		return ${ret}
+	fi
+	cmp "$tmpin" "$tmpout" > /dev/null 2>&1
+	if [ $? -ne 0 ]; then
+		echo "[ FAIL ]"
+		ls -l "$tmpin" "$tmpout" 1>&2
+	else
+		echo "[  OK  ]"
+	fi
+}
+
+trap cleanup EXIT
+
+SIZE=$((RANDOM % (1024 * 1024)))
+if [ $SIZE -eq 0 ]; then
+	SIZE=1
+fi
+
+dd if=/dev/urandom of="$tmpin" bs=1 count=$SIZE 2> /dev/null
+
+check_transfer MPTCP MPTCP
+check_transfer MPTCP TCP
+check_transfer TCP MPTCP
+
+exit 0
-- 
2.22.0


  parent reply	other threads:[~2019-06-17 22:59 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-06-17 22:57 [RFC PATCH net-next 00/33] Multipath TCP Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 01/33] tcp: Add MPTCP option number Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 02/33] tcp: Define IPPROTO_MPTCP Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 03/33] mptcp: Add MPTCP socket stubs Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 04/33] mptcp: Handle MPTCP TCP options Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 05/33] mptcp: Associate MPTCP context with TCP socket Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 06/33] tcp: Expose tcp struct and routine for MPTCP Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 07/33] mptcp: Handle MP_CAPABLE options for outgoing connections Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 08/33] mptcp: add mptcp_poll Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 09/33] tcp, ulp: Add clone operation to tcp_ulp_ops Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 10/33] mptcp: Create SUBFLOW socket for incoming connections Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 11/33] mptcp: Add key generation and token tree Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 12/33] mptcp: Add shutdown() socket operation Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 13/33] mptcp: Add setsockopt()/getsockopt() socket operations Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 14/33] tcp: clean ext on tx recycle Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 15/33] mptcp: Add MPTCP to skb extensions Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 16/33] tcp: Prevent coalesce/collapse when skb has MPTCP extensions Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 17/33] tcp: Export low-level TCP functions Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 18/33] mptcp: Write MPTCP DSS headers to outgoing data packets Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 19/33] mptcp: Implement MPTCP receive path Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 20/33] mptcp: Make connection_list a real list of subflows Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 21/33] mptcp: add and use mptcp_subflow_hold Mat Martineau
2019-06-17 22:57 ` Mat Martineau [this message]
2019-06-17 22:57 ` [RFC PATCH net-next 23/33] mptcp: selftests: switch to netns+veth based tests Mat Martineau
2019-06-17 22:57 ` [RFC PATCH net-next 24/33] mptcp: selftests: Add capture option Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 25/33] mptcp: use sk_page_frag() in sendmsg Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 26/33] mptcp: sendmsg() do spool all the provided data Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 27/33] mptcp: allow collapsing consecutive sendpages on the same substream Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 28/33] tcp: Check for filled TCP option space before SACK Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 29/33] mptcp: accept: don't leak mptcp socket structure Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 30/33] mptcp: switch sublist to mptcp socket lock protection Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 31/33] mptcp: Add path manager interface Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 32/33] mptcp: Add ADD_ADDR handling Mat Martineau
2019-06-17 22:58 ` [RFC PATCH net-next 33/33] mptcp: Add handling of incoming MP_JOIN requests Mat Martineau

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190617225808.665-23-mathew.j.martineau@linux.intel.com \
    --to=mathew.j.martineau@linux.intel.com \
    --cc=cpaasch@apple.com \
    --cc=dcaratti@redhat.com \
    --cc=edumazet@google.com \
    --cc=fw@strlen.de \
    --cc=matthieu.baerts@tessares.net \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=peter.krystad@linux.intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).