Index | Thread | Search

From:
Jan Klemkow <jan@openbsd.org>
Subject:
Re: tcpbench + tls
To:
tech@openbsd.org
Date:
Wed, 24 Jul 2024 09:19:47 +0200

Download raw body.

Thread
On Wed, Jul 24, 2024 at 06:47:27AM +0100, Jason McIntyre wrote:
> On Tue, Jul 23, 2024 at 07:16:09PM +0200, Jan Klemkow wrote:
> > 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?
> 
> hi. some tweaks inline:

Fixed.

Thanks,
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	24 Jul 2024 07:16:03 -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	24 Jul 2024 07:16:18 -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,22 @@ 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
+specifies 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) or
+.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 .
 .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	24 Jul 2024 07:16:03 -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");