Index | Thread | Search

From:
Jan Klemkow <jan@openbsd.org>
Subject:
Re: tcpbench + tls
To:
Theo Buehler <tb@theobuehler.org>
Cc:
tech@openbsd.org
Date:
Mon, 4 Nov 2024 17:59:55 +0100

Download raw body.

Thread
On Fri, Aug 02, 2024 at 11:04:19PM GMT, Jan Klemkow wrote:
> On Fri, Aug 02, 2024 at 10:55:09PM +0200, Jan Klemkow wrote:
> > On Wed, Jul 24, 2024 at 10:34:19AM +0200, Theo Buehler wrote:
> > > On Wed, Jul 24, 2024 at 09:19:47AM +0200, Jan Klemkow wrote:
> > > > 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.
> > > 
> > > While I understand the desire of benchmarking TLS and making this as
> > > easy as possible for the benchmarker, I'm not a fan of generating an
> > > (invalid per RFC 5280) certificate on the fly.
> > 
> > I would prefer to generate a certificate by default for ease of use.
> > But, I applied all your improvements.

Here is a version of this diff without on the fly generation of
certificate and key.  So, this diff is much smaller and we can do this
afterward.

ok?

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	4 Nov 2024 16:53:23 -0000
@@ -1,7 +1,7 @@
 #	$OpenBSD: Makefile,v 1.10 2022/08/15 09:06:54 claudio Exp $
 
 PROG=	tcpbench
-LDADD=	-lm -levent
-DPADD=	${LIBM} ${LIBEVENT}
+LDADD=	-lm -levent -ltls -lcrypto
+DPADD=	${LIBM} ${LIBEVENT} ${LIBTLS} ${LIBCRYPTO}
 
 .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	4 Nov 2024 16:52:17 -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,21 @@
 .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 C Ar certfile Fl K Ar keyfile
 .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 +112,16 @@ 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 C Ar certfile
+Load the public key part of the TLS peer certificate from
+.Ar certfile ,
+in PEM format.
+Requires
+.Fl s
+and
+.Fl c .
 .It Fl D
 Enable debugging on the socket.
 .It Fl k Ar kvars
@@ -118,6 +129,14 @@ Specify one or more kernel variables to 
 separated with commas.
 This option is only valid in TCP mode.
 The default is not to monitor any variables.
+.It Fl K Ar keyfile
+Load the TLS private key from
+.Ar keyfile ,
+in PEM format.
+Requires
+.Fl s
+and
+.Fl c .
 .It Fl l
 List the name of kernel variables available for monitoring and exit.
 .It Fl n Ar connections
@@ -143,9 +162,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 +177,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	4 Nov 2024 16:52:17 -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] [-C certfile -K keyfile] [-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,39 @@ 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;
+}
+
+int
 main(int argc, char **argv)
 {
 	struct timeval tv;
@@ -990,11 +1107,14 @@ 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;
 	struct sockaddr_un sock_un;
+	char *crtfile = NULL, *keyfile = NULL;
+	uint8_t *crt = NULL, *key = NULL;
+	size_t key_size, crt_size;
 
 	/* Init world */
 	setvbuf(stdout, NULL, _IOLBF, 0);
@@ -1005,11 +1125,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:cC:Dhlk:K:n:p:Rr:sS:t:T:uUvV:"))
 	    != -1) {
 		switch (ch) {
 		case '4':
@@ -1021,6 +1142,12 @@ main(int argc, char **argv)
 		case 'b':
 			srcbind = optarg;
 			break;
+		case 'c':
+			usetls = 1;
+			break;
+		case 'C':
+			crtfile = optarg;
+			break;
 		case 'D':
 			ptb->Dflag = 1;
 			break;
@@ -1033,6 +1160,9 @@ main(int argc, char **argv)
 			ptb->kvars = check_prepare_kvars(tmp);
 			free(tmp);
 			break;
+		case 'K':
+			keyfile = optarg;
+			break;
 		case 'R':
 			ptb->Rflag = 1;
 			break;
@@ -1088,6 +1218,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,9 +1250,19 @@ 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 && usetls && (crtfile == NULL || keyfile == NULL))
+		usage();
+
+	if (crtfile != NULL && keyfile != NULL) {
+		if ((crt = tls_load_file(crtfile, &crt_size, NULL)) == NULL)
+			err(1, "tls_load_file");
+		if ((key = tls_load_file(keyfile, &key_size, NULL)) == NULL)
+			err(1, "tls_load_file");
+	}
+
 	if (!ptb->sflag || ptb->Uflag)
 		mainstats.host = host = argv[0];
 
@@ -1200,6 +1342,33 @@ main(int argc, char **argv)
 
 	if (pledge("stdio inet unix", NULL) == -1)
 		err(1, "pledge");
+
+	if (usetls) {
+		uint32_t protocols = 0;
+
+		if ((ptb->tls_cfg = tls_config_new()) == NULL)
+			errx(1, "unable to allocate TLS config");
+
+		if (ptb->sflag) {
+			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));
+	}
 
 	/* Init world */
 	TAILQ_INIT(&sc_queue);