Index | Thread | Search

From:
Jan Klemkow <jan@openbsd.org>
Subject:
tcpbench + tls
To:
tech@openbsd.org
Date:
Tue, 23 Jul 2024 19:16:09 +0200

Download raw body.

Thread
Hi,

This diff adds TLS support via libtls to tcpbench.  So, we are able to
measure TLS throughput performance.

Its based on the tls code of nc(1).  But, I just took the essentials
which is needed in the context of tcpbench.  We just transfer test data
and have no need for authentication nor key handling.  In client mode,
it just accepts every certificate.  In server mode, it creates a self
signed certificate on the fly.  Thus, its easy to use.

ok?

bye,
Jan

Index: Makefile
===================================================================
RCS file: /cvs/src/usr.bin/tcpbench/Makefile,v
diff -u -p -r1.10 Makefile
--- Makefile	15 Aug 2022 09:06:54 -0000	1.10
+++ Makefile	23 Jul 2024 17:02:46 -0000
@@ -1,7 +1,7 @@
 #	$OpenBSD: Makefile,v 1.10 2022/08/15 09:06:54 claudio Exp $
 
 PROG=	tcpbench
-LDADD=	-lm -levent
+LDADD=	-lm -levent -ltls -lcrypto
 DPADD=	${LIBM} ${LIBEVENT}
 
 .include <bsd.prog.mk>
Index: tcpbench.1
===================================================================
RCS file: /cvs/src/usr.bin/tcpbench/tcpbench.1,v
diff -u -p -r1.30 tcpbench.1
--- tcpbench.1	15 Aug 2022 09:06:54 -0000	1.30
+++ tcpbench.1	23 Jul 2024 17:02:46 -0000
@@ -24,7 +24,7 @@
 .Nm
 .Fl l
 .Nm
-.Op Fl 46DRUuv
+.Op Fl 46cDRUuv
 .Op Fl B Ar buf
 .Op Fl b Ar sourceaddr
 .Op Fl k Ar kvars
@@ -32,20 +32,20 @@
 .Op Fl p Ar port
 .Op Fl r Ar interval
 .Op Fl S Ar space
-.Op Fl T Ar toskeyword
+.Op Fl T Ar keyword
 .Op Fl t Ar secs
 .Op Fl V Ar rtable
 .Ar hostname
 .Nm
 .Bk -words
 .Fl s
-.Op Fl 46DUuv
+.Op Fl 46cDUuv
 .Op Fl B Ar buf
 .Op Fl k Ar kvars
 .Op Fl p Ar port
 .Op Fl r Ar interval
 .Op Fl S Ar space
-.Op Fl T Ar toskeyword
+.Op Fl T Ar keyword
 .Op Fl V Ar rtable
 .Op Ar hostname
 .Ek
@@ -111,6 +111,8 @@ stream.
 .It Fl b Ar sourceaddr
 Specify the IP address to send the packets from,
 which is useful on machines with multiple interfaces.
+.It Fl c
+Use TLS to connect or listen.
 .It Fl D
 Enable debugging on the socket.
 .It Fl k Ar kvars
@@ -143,9 +145,9 @@ connections.
 It defaults to using TCP if
 .Fl u
 is not specified.
-.It Fl T Ar toskeyword
+.It Fl T Ar keyword
 Change the IPv4 TOS or IPv6 TCLASS value.
-.Ar toskeyword
+.Ar keyword
 may be one of
 .Ar critical ,
 .Ar inetcontrol ,
@@ -158,6 +160,23 @@ or one of the DiffServ Code Points:
 .Ar af11 ... af43 ,
 .Ar cs0 ... cs7 ;
 or a number in either hex or decimal.
+.Pp
+For TLS options,
+.Ar keyword
+specify a value in the form of a
+.Ar key Ns = Ns Ar value
+pair:
+.Cm ciphers ,
+which allows the supported TLS ciphers to be specified (see
+.Xr tls_config_set_ciphers 3
+for further details);
+.Cm protocols ,
+which allows the supported TLS protocols to be specified (see
+.Xr tls_config_parse_protocols 3
+for further details).
+Specifying TLS options requires
+.Fl c .
+.Pp
 .It Fl t Ar secs
 Stop after
 .Ar secs
Index: tcpbench.c
===================================================================
RCS file: /cvs/src/usr.bin/tcpbench/tcpbench.c,v
diff -u -p -r1.70 tcpbench.c
--- tcpbench.c	21 Mar 2024 16:46:04 -0000	1.70
+++ tcpbench.c	23 Jul 2024 17:02:46 -0000
@@ -51,6 +51,12 @@
 #include <poll.h>
 #include <paths.h>
 #include <math.h>
+#include <tls.h>
+
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
 
 #define DEFAULT_PORT "12345"
 #define DEFAULT_STATS_INTERVAL 1000 /* ms */
@@ -74,6 +80,7 @@ struct {
 	char	**kvars;	/* Kvm enabled vars */
 	char	 *dummybuf;	/* IO buffer */
 	size_t	  dummybuf_len;	/* IO buffer len */
+	struct tls_config *tls_cfg;
 } tcpbench, *ptb;
 
 struct tcpservsock {
@@ -95,8 +102,12 @@ struct statctx {
 	struct tcpservsock *tcp_ts;
 	/* UDP only */
 	u_long udp_slice_pkts;
+	/* TLS context */
+	struct tls *tls;
 };
 
+char *tls_ciphers;
+char *tls_protocols;
 struct statctx *udp_sc; /* singleton */
 
 static void	signal_handler(int, short, void *);
@@ -196,11 +207,11 @@ usage(void)
 {
 	fprintf(stderr,
 	    "usage: tcpbench -l\n"
-	    "       tcpbench [-46DRUuv] [-B buf] [-b sourceaddr] [-k kvars] [-n connections]\n"
-	    "                [-p port] [-r interval] [-S space] [-T toskeyword]\n"
+	    "       tcpbench [-46cDRUuv] [-B buf] [-b sourceaddr] [-k kvars] [-n connections]\n"
+	    "                [-p port] [-r interval] [-S space] [-T keyword]\n"
 	    "                [-t secs] [-V rtable] hostname\n"
-	    "       tcpbench -s [-46DUuv] [-B buf] [-k kvars] [-p port] [-r interval]\n"
-	    "                [-S space] [-T toskeyword] [-V rtable] [hostname]\n");
+	    "       tcpbench -s [-46cDUuv] [-B buf] [-k kvars] [-p port] [-r interval]\n"
+	    "                [-S space] [-T keyword] [-V rtable] [hostname]\n");
 	exit(1);
 }
 
@@ -592,8 +603,13 @@ tcp_server_handle_sc(int fd, short event
 	struct statctx *sc = v_sc;
 	ssize_t n;
 
-	n = read(sc->fd, sc->buf, sc->buflen);
+	if (sc->tls)
+		n = tls_read(sc->tls, sc->buf, sc->buflen);
+	else
+		n = read(sc->fd, sc->buf, sc->buflen);
 	if (n == -1) {
+		if (sc->tls)
+			err(1, "tls_read: %s", tls_error(sc->tls));
 		if (errno != EINTR && errno != EWOULDBLOCK)
 			warn("fd %d read error", sc->fd);
 		return;
@@ -623,6 +639,33 @@ tcp_server_handle_sc(int fd, short event
 	mainstats.total_bytes += n;
 }
 
+int
+timeout_tls(int s, struct tls *tls_ctx, int (*func)(struct tls *))
+{
+	struct pollfd pfd;
+	int ret;
+
+	while ((ret = (*func)(tls_ctx)) != 0) {
+		if (ret == TLS_WANT_POLLIN)
+			pfd.events = POLLIN;
+		else if (ret == TLS_WANT_POLLOUT)
+			pfd.events = POLLOUT;
+		else
+			break;
+		pfd.fd = s;
+		if ((ret = poll(&pfd, 1, -1)) == 1)
+			continue;
+		else if (ret == 0) {
+			errno = ETIMEDOUT;
+			ret = -1;
+			break;
+		} else
+			err(1, "poll failed");
+	}
+
+	return ret;
+}
+
 static void
 tcp_server_accept(int fd, short event, void *arg)
 {
@@ -632,6 +675,15 @@ tcp_server_accept(int fd, short event, v
 	struct sockaddr_storage ss;
 	socklen_t sslen;
 	char tmp[NI_MAXHOST + 2 + NI_MAXSERV];
+	static struct tls *tls = NULL;
+
+	if (ptb->tls_cfg && tls == NULL) {
+		tls = tls_server();
+		if (tls == NULL)
+			err(1, "Unable to create TLS context.");
+		if (tls_configure(tls, ptb->tls_cfg) == -1)
+			errx(1, "tls_configure: %s", tls_error(tls));
+	}
 
 	sslen = sizeof(ss);
 
@@ -672,6 +724,8 @@ tcp_server_accept(int fd, short event, v
 	sc->tcp_ts = ts;
 	sc->fd = sock;
 	stats_prepare(sc);
+	if (tls && tls_accept_socket(tls, &sc->tls, sc->fd) == -1)
+		err(1, "tls_accept_socket: %s", tls_error(tls));
 
 	event_set(&sc->ev, sc->fd, EV_READ | EV_PERSIST,
 	    tcp_server_handle_sc, sc);
@@ -786,7 +840,14 @@ client_handle_sc(int fd, short event, vo
 
 	if (ptb->Rflag)
 		blen = arc4random_uniform(blen) + 1;
-	if ((n = write(sc->fd, sc->buf, blen)) == -1) {
+
+	if (sc->tls)
+		n = tls_write(sc->tls, sc->buf, blen);
+	else
+		n = write(sc->fd, sc->buf, blen);
+	if (n == -1) {
+		if (sc->tls)
+			warn("tls_write: %s", tls_error(sc->tls));
 		if (errno == EINTR || errno == EWOULDBLOCK ||
 		    (UDP_MODE && errno == ENOBUFS))
 			return;
@@ -885,6 +946,29 @@ client_init(struct addrinfo *aitop, int 
 			sc = udp_sc;
 
 		sc->fd = sock;
+
+		if (ptb->tls_cfg) {
+			sc->tls = tls_client();
+			if (sc->tls == NULL)
+				err(1, "Unable to create TLS context.");
+
+			if (tls_configure(sc->tls, ptb->tls_cfg) == -1)
+				errx(1, "tls_configure: %s",
+				    tls_error(sc->tls));
+
+			if (tls_connect_socket(sc->tls, sc->fd,
+			    mainstats.host) == -1)
+				errx(1, "tls_connect_socket: %s",
+				    tls_error(sc->tls));
+			if (timeout_tls(sc->fd, sc->tls, tls_handshake) == -1) {
+				const char *errstr;
+
+				if ((errstr = tls_error(sc->tls)) == NULL)
+					errstr = strerror(errno);
+				errx(1, "tls handshake failed (%s)", errstr);
+			}
+		}
+
 		stats_prepare(sc);
 
 		event_set(&sc->ev, sc->fd, EV_WRITE | EV_PERSIST,
@@ -982,6 +1066,97 @@ wrapup(int err)
 }
 
 int
+process_tls_opt(char *s)
+{
+	size_t len;
+	char *v;
+
+	const struct tlskeywords {
+		const char	 *keyword;
+		char		**value;
+	} *t, tlskeywords[] = {
+		{ "ciphers",	&tls_ciphers },
+		{ "protocols",	&tls_protocols },
+		{ NULL,		NULL },
+	};
+
+	len = strlen(s);
+	if ((v = strchr(s, '=')) != NULL) {
+		len = v - s;
+		v++;
+	}
+
+	for (t = tlskeywords; t->keyword != NULL; t++) {
+		if (strlen(t->keyword) == len &&
+		    strncmp(s, t->keyword, len) == 0) {
+			if (v == NULL)
+				errx(1, "invalid tls value `%s'", s);
+			*t->value = v;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+void
+generate_key(uint8_t **key, size_t *key_size, uint8_t **crt, size_t *crt_size)
+{
+	EVP_PKEY	*pkey;
+	BIGNUM		*bne;
+	RSA		*rsa;
+	X509		*x509;
+	BIO		*bio;
+
+	/*
+	 * Generate RSA key.
+	 */
+	if ((pkey = EVP_PKEY_new()) == NULL)
+		err(1, "EVP_PKEY_new");
+	if ((bne = BN_new()) == NULL)
+		err(1, "BN_new");
+	if (BN_set_word(bne, RSA_F4) == 0)
+		err(1, "BN_set_word");
+	if ((rsa = RSA_new()) == NULL)
+		err(1, "RSA_new");
+	if (RSA_generate_key_ex(rsa, 2048, bne, NULL) == 0)
+		err(1, "RSA_generate_key_ex");
+	if (EVP_PKEY_assign_RSA(pkey, rsa) == 0)
+		err(1, "EVP_PKEY_assign_RSA");
+
+	/* Get memory pointer of RSA key. */
+	if ((bio = BIO_new(BIO_s_mem())) == NULL)
+		err(1, "BIO_new");
+	if (!PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, NULL, NULL))
+		err(1, "PEM_write_bio_PrivateKey");
+	if ((*key_size = BIO_get_mem_data(bio, key)) <= 0)
+		err(1, "BIO_get_mem_data");
+
+	/*
+	 * Generate self sign certificate.
+	 */
+	if ((x509 = X509_new()) == NULL)
+		err(1, "X509_new");
+
+	/* Expiration date: 30 days (60s * 60m * 24h * 30d) */
+	X509_gmtime_adj(X509_get_notBefore(x509), 0);
+	X509_gmtime_adj(X509_get_notAfter(x509), 2592000);
+
+	/* Sign the certificate with the key. */
+	if (X509_set_pubkey(x509, pkey) == 0)
+		err(1, "X509_set_pubkey");
+	if (X509_sign(x509, pkey, EVP_sha256()) == 0)
+		err(1, "X509_sign");
+
+	/* Get memory pointer of certificate. */
+	if ((bio = BIO_new(BIO_s_mem())) == NULL)
+		err(1, "BIO_new");
+	if (!PEM_write_bio_X509(bio, x509))
+		err(1, "PEM_write_bio_PrivateKey");
+	if ((*crt_size = BIO_get_mem_data(bio, crt)) <= 0)
+		err(1, "BIO_get_mem_data");
+}
+
+int
 main(int argc, char **argv)
 {
 	struct timeval tv;
@@ -990,7 +1165,7 @@ main(int argc, char **argv)
 	struct addrinfo *aitop, *aib, hints;
 	const char *errstr;
 	struct rlimit rl;
-	int ch, herr, nconn;
+	int ch, herr, nconn, usetls = 0;
 	int family = PF_UNSPEC;
 	const char *host = NULL, *port = DEFAULT_PORT, *srcbind = NULL;
 	struct event ev_sigint, ev_sigterm, ev_sighup, ev_siginfo, ev_progtimer;
@@ -1005,11 +1180,12 @@ main(int argc, char **argv)
 	ptb->kvars = NULL;
 	ptb->rflag = DEFAULT_STATS_INTERVAL;
 	ptb->Tflag = -1;
+	ptb->tls_cfg = NULL;
 	nconn = 1;
 	aib = NULL;
 	secs = 0;
 
-	while ((ch = getopt(argc, argv, "46b:B:Dhlk:n:p:Rr:sS:t:T:uUvV:"))
+	while ((ch = getopt(argc, argv, "46b:B:cDhlk:n:p:Rr:sS:t:T:uUvV:"))
 	    != -1) {
 		switch (ch) {
 		case '4':
@@ -1021,6 +1197,9 @@ main(int argc, char **argv)
 		case 'b':
 			srcbind = optarg;
 			break;
+		case 'c':
+			usetls = 1;
+			break;
 		case 'D':
 			ptb->Dflag = 1;
 			break;
@@ -1088,6 +1267,8 @@ main(int argc, char **argv)
 			ptb->Uflag = 1;
 			break;
 		case 'T':
+			if (process_tls_opt(optarg))
+				break;
 			if (map_tos(optarg, &ptb->Tflag))
 				break;
 			errstr = NULL;
@@ -1118,7 +1299,7 @@ main(int argc, char **argv)
 	argv += optind;
 	argc -= optind;
 	if ((argc != (ptb->sflag && !ptb->Uflag ? 0 : 1)) ||
-	    (UDP_MODE && (ptb->kvars || nconn != 1)))
+	    (UDP_MODE && (ptb->kvars || nconn != 1 || usetls)))
 		usage();
 
 	if (!ptb->sflag || ptb->Uflag)
@@ -1127,6 +1308,40 @@ main(int argc, char **argv)
 	if (ptb->Uflag)
 		if (unveil(host, "rwc") == -1)
 			err(1, "unveil %s", host);
+
+	if (usetls) {
+		uint8_t		*key;
+		uint8_t		*crt;
+		size_t		 key_size;
+		size_t		 crt_size;
+		uint32_t	 protocols = 0;
+
+		if ((ptb->tls_cfg = tls_config_new()) == NULL)
+			errx(1, "unable to allocate TLS config");
+
+		if (ptb->sflag) {
+			/* Generate key with selfsigned certificate. */
+			generate_key(&key, &key_size, &crt, &crt_size);
+
+			if (tls_config_set_key_mem(ptb->tls_cfg, key,
+			    key_size) == -1)
+				errx(1, "%s", tls_config_error(ptb->tls_cfg));
+			if (tls_config_set_cert_mem(ptb->tls_cfg, crt,
+			    crt_size) == -1)
+				errx(1, "%s", tls_config_error(ptb->tls_cfg));
+		} else {
+			/* Don't check server certificate. */
+			tls_config_insecure_noverifyname(ptb->tls_cfg);
+			tls_config_insecure_noverifycert(ptb->tls_cfg);
+		}
+
+		if (tls_config_parse_protocols(&protocols, tls_protocols) == -1)
+			errx(1, "invalid TLS protocols `%s'", tls_protocols);
+		if (tls_config_set_protocols(ptb->tls_cfg, protocols) == -1)
+			errx(1, "%s", tls_config_error(ptb->tls_cfg));
+		if (tls_config_set_ciphers(ptb->tls_cfg, tls_ciphers) == -1)
+			errx(1, "%s", tls_config_error(ptb->tls_cfg));
+	}
 
 	if (pledge("stdio id dns inet unix", NULL) == -1)
 		err(1, "pledge");