From: Jan Klemkow Subject: tcpbench + tls To: tech@openbsd.org Date: Tue, 23 Jul 2024 19:16:09 +0200 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 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 #include #include +#include + +#include +#include +#include +#include #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");