Thread (19 messages) 19 messages, 2 authors, 2021-01-07

Re: [PATCH v2 1/3] btrfs-progs: add Kernel TLS to btrfs send/receive

From: Sheng Mao <hidden>
Date: 2021-01-07 03:07:48

Hi Yugui,

I know what's the cause now: your btrfs-send has been paused by kernel
for some reason. I think it is related to the -ERESTARTSYS handling in
write_buf() in fs/btrfs/send.c. If you let btrfs_ioctl_send directly
write to a socket, it behaves the same as pipe/FIFO. But if you let it
directly write to a file (with -f filename), then there is a chance
that you cannot interrupt ioctl: pstack will show after ioctl finish
running.

You can simulate pstack behavior with kill -SIGSTOP or -SIGTSTP and
later kill -SIGCONT. After doing that, you can see btrfs_ioctl_send
restarts. Then you can see in the buffer (if you dump it to a file, or
show buffer content on btrfs-receive) that the content is
"btrfs-stream", the magic header string.

There is a similar case from Chris Murphy on "5.6, slow send/receive,
< 1MB/s" [0]. He did strace and also restarted ioctl. The error
message: ERROR: command length 1718776930 confirms the issue.
1718776930 is [f, r, t, b] in little endian.

Later, I tried to put printk in custom kernel and it eventually
confirms that ioctl restarts from beginning.

So, the question is: what/who/why pauses your btrfs-send on sending
the large subvol. If it is some app sending SIGTSTP, we can try to
ignore it before ioctl starting; for SIGSTOP, we cannot do anything now.

For long term, the -ERESTARTSYS will be fixed (I saw the comments
there).

Regards,
Sheng



[0]: https://www.spinics.net/lists/linux-btrfs/msg101006.html


On Sun, Jan 03, 2021 at 11:25:36PM -0700, Sheng Mao wrote:
Hi Yugui,

I can reproduce the issue with your pstack trick. Many thanks! I am
trying to use a larger SEND_BUFFER_SIZE. The strange thing is socket
should never send wrong data. Thanks for the insight, my guess is
scheduling of the splicing-thread causes the problem. I will do more
investigation.

I think your test/benchmark plan is great. For slow network testing,
you can use mbuffer to replace nc.

Regards,
Sheng


On Mon, Jan 04, 2021 at 12:59:11PM +0800, Wang Yugui wrote:
quoted
Hi, Sheng
quoted
Hi Yugui,

Thanks for the testing! The error message from your two failed cases
shows that sender is still working on splice. So I made two patches:

- directly write to socket, instead of writing to pipe: on my
computer, it is much slower to do so
- enlarge pipe buffer size

I am not sure whether these two can help debugging or not.
a 'pstack pid' command just change the os scheduler for 'btrfs send'.
so this should be a thread-sync problem or a pipe full case or pipe
empty case.

maybe some nanosleep(random value) will help to debug this problem.
quoted
BTW, you run centos 8.3 on both server and client, I guess they are
backported from latest 5.x kernel. May I know what version is it? 
kernel of btrfs send side:5.4.86
kernel of btrfs recv side: 5.10.4

most of our servers are running kernel 5.4.86, 
and just one is running 5.10.4
and all are built on centos7 with devtoolset-8(gcc 8.3.1 20190311)

By the way, I am new to btrfs send/receive on different server,

for tls mode, we compare it to the usage based on ssh?
	ssh T620 btrfs send /archive/movie2/ | btrfs receive /btrfs/
this usage based on ssh is very easy.

for tcp mode, we compare it to the usage based on ncat?
server: ncat -l -p 8080 | btrfs receive
client:  btrfs send | ncat server 8080

Best Regards
Wang Yugui (wangyugui@e16-tech.com)
2021/01/04
quoted
Regards,
Sheng


On Sun, Jan 03, 2021 at 07:19:28PM +0800, Wang Yugui wrote:
quoted
Hi, Sheng

Now we can reproduce this CRC error.

client is running
	/usr/bin/time btrfs-5.9 send --conn-addr 10.0.0.76 --tcp-port 8080 /archive/movie2

server is running
	/usr/bin/time btrfs-5.9 receive --listen-addr :: --tcp-port 8080 --dump

On client,  the command pstack 13623(pid of btrfs-5.9 send') is slow,
and then 'ERROR: crc32 mismatch in command' happen in server side.

A little difficult to understand.

It seems thread-sync problem in client side?

Best Regards
Wang Yugui (wangyugui@e16-tech.com)
2021/01/03
quoted
Hi, Sheng

I test it again with this patch, and huge performance improvement as
reported!
quoted
I have made a patch to fix it:
https://patchwork.kernel.org/project/linux-btrfs/patch/20201226214606.49241-1-shngmao@gmail.com/

The benchmark result is recorded in github issue #324:
https://github.com/kdave/btrfs-progs/issues/324#issuecomment-751813175
but sometimes there is 'ERROR: crc32 mismatch in command'.

This is a 190G subvolume to send/receive, and then sometime slient error
in transfer?

failed case 1:
[root@T620 btrfs-progs]# ./test.send.listen-addr.sh 
+ server=10.0.0.76
+ sleep 2
+ ssh 10.0.0.76 /usr/bin/time btrfs-5.9 receive --listen-addr :: --tcp-port 8080 --dump
+ /usr/bin/time btrfs-5.9 send --conn-addr 10.0.0.76 --tcp-port 8080 /archive/movie2
ERROR: crc32 mismatch in command
ERROR: failed to dump the send stream: Invalid argument
Command exited with non-zero status 1
28.39user 49.96system 2:02.01elapsed 64%CPU (0avgtext+0avgdata 5920maxresident)k
96inputs+0outputs (1major+390minor)pagefaults 0swaps
+ sleep 1
+ cat time.send
At subvol /archive/movie2
ERROR: failed to read stream from kernel: Connection reset by peer
Command exited with non-zero status 104
0.30user 130.54system 2:00.35elapsed 108%CPU (0avgtext+0avgdata 5564maxresident)k
192302416inputs+16outputs (1major+457minor)pagefaults 0swaps

failed case 2:
[root@T620 btrfs-progs]# ./test.send.listen-addr.sh 
+ server=10.0.0.76
+ sleep 2
+ ssh 10.0.0.76 /usr/bin/time btrfs-5.9 receive --listen-addr :: --tcp-port 8080 --dump
+ /usr/bin/time btrfs-5.9 send --conn-addr 10.0.0.76 --tcp-port 8080 /archive/movie2
ERROR: crc32 mismatch in command
ERROR: failed to dump the send stream: Invalid argument
Command exited with non-zero status 1
28.40user 48.62system 1:39.13elapsed 77%CPU (0avgtext+0avgdata 5920maxresident)k
0inputs+0outputs (0major+320minor)pagefaults 0swaps
+ sleep 1
+ cat time.send
At subvol /archive/movie2
ERROR: failed to read stream from kernel: Connection reset by peer




OK case 1:

+ server=10.0.0.76
+ ssh 10.0.0.76 btrfs subvolume delete -c /btrfs/movie2
Delete subvolume (commit): '/btrfs/movie2'
+ sleep 2
+ ssh 10.0.0.76 /usr/bin/time btrfs-5.9 receive --listen-addr :: --tcp-port 8080 /btrfs
+ /usr/bin/time btrfs-5.9 send --conn-addr 10.0.0.76 --tcp-port 8080 /archive/movie2
At subvol movie2
+ sleep 1
47.65user 267.49system 5:20.98elapsed 98%CPU (0avgtext+0avgdata 5828maxresident)k
96inputs+396806624outputs (1major+509minor)pagefaults 0swaps
+ cat time.send
At subvol /archive/movie2
0.52user 249.44system 5:19.27elapsed 78%CPU (0avgtext+0avgdata 5740maxresident)k
174679448inputs+8outputs (1major+457minor)pagefaults 0swaps


OK case 1:

+ server=10.0.0.76
+ sleep 2
+ ssh 10.0.0.76 /usr/bin/time btrfs-5.9 receive --listen-addr :: --tcp-port 8080 --dump
+ /usr/bin/time btrfs-5.9 send --conn-addr 10.0.0.76 --tcp-port 8080 /archive/movie2
61.53user 104.62system 3:21.60elapsed 82%CPU (0avgtext+0avgdata 5684maxresident)k
0inputs+0outputs (0major+313minor)pagefaults 0swaps
+ sleep 1
+ cat time.send
At subvol /archive/movie2
0.60user 231.95system 3:19.91elapsed 116%CPU (0avgtext+0avgdata 5768maxresident)k
66606224inputs+8outputs (0major+394minor)pagefaults 0swaps


Best Regards
Wang Yugui (wangyugui@e16-tech.com)
2021/01/03
quoted
Hi Yugui,

Thank you for the benchmark!

I forgot to mention the CRC. There is a faster version of CRC but
current receive buffer `btrfs_send_stream::read_buf` is not aligned
to unsigned long.

I have made a patch to fix it:
https://patchwork.kernel.org/project/linux-btrfs/patch/20201226214606.49241-1-shngmao@gmail.com/

The benchmark result is recorded in github issue #324:
https://github.com/kdave/btrfs-progs/issues/324#issuecomment-751813175

In addition, on the server side, would you mind to try:

btrfs-5.9 receive --listen-addr :: --tcp-port 8080 --dump &> /dev/null

--dump only writes to stdout thus we can see network-only performance.

Thank you!

On Sat, Jan 02, 2021 at 06:45:32PM +0800, Wang Yugui wrote:
quoted
To: Sheng

some test result on 10Gbps.

Server:dell Precision T7610 Xeon(R) CPU E5-2660 v2
Client: dell PowerEdge T620 Xeon(R) CPU E5-2680 v2
OS: centos 8.3(gnutls-3.6.14-6.el8.x86_64)

test script:
server=10.0.0.76
ssh ${server} btrfs-5.9 subvolume delete /btrfs/movie2
ssh ${server} /usr/bin/time btrfs-5.9 receive --listen-addr :: --tcp-port 8080 /btrfs &
sleep 2
/usr/bin/time btrfs-5.9 send --conn-addr ${server} --tcp-port 8080 /archive/movie2 2>time.send
sleep 1
cat time.send

test result:
It works with about 200 MB/s. slow than expacted.
top result:
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
   7479 root      20   0   45568   5200   4540 R 100.0   0.0   8:16.05 btrfs-5.9 receive --listen-addr

#pstack 7479
#0  __crc32c_le (crc=2848417421, data=0x7ffe68ee038b "\277g\276+\022\nZ", length=<optimized out>) at crypto/crc32c.c:213
#1  0x00000000004637ed in crc32c_le (crc=crc@entry=0, data=data@entry=0x7ffe68edb004 "O\300", length=length@entry=49241) at crypto/crc32c.c:223
#2  0x000000000045b71a in read_cmd (sctx=0x7ffe68edb000) at common/send-stream.c:141
#3  read_and_process_cmd (sctx=0x7ffe68edb000) at common/send-stream.c:330

That is to say, for high speed network/disk config,  the current bottleneck
of btrfs send/receive is the CRC process in btrfs receive side.

Best Regards
Wang Yugui (wangyugui@e16-tech.com)
2021/01/02
quoted
From: Sheng Mao <redacted>

Currently, btrfs send outputs to a pipe or a file;
btrfs receive inputs from a pipe or a file.
The pipe can be a SSH or a stunnel connection.
btrfs send/receive itself doesn't handle any connection.

Kernel introduces TLS in version 4.13 (referred as ktls).
Ktls provides a transparent TLS 1.2/1.3 connection:
from user space aspect, applications use a normal socket
fd to read/write from/to. This model fits into btrfs send's
design well: btrfs first writes to a pipe and then splices
data from pipe to the final fd (which is a file or pipe).
Ktls simply replaces the final fd with the ktls socket fd.
According to ktls' author, ktls can boost performance for
2~7%. Ktls helps less on receiving side: btrfs receive
processes data in user space. But btrfs receive still can
use transparent TLS layer.

I have implemented ktls for btrfs send/receive. Here are
the key features:

- Use GnuTLS for handshake. OpenSSL is not suitable for this
task, we need to pass IV and key from handshake session to
kernel setting.
- Use PSK (pre-shared key) for handshake. User can use --key
to use a key file or input password on prompt.
- Key file is checked as PEM format first; if it fails, key
file is treated as raw binary file.
- Three TLS modes are supported: none (raw TCP), TLS 1.2 + GCM
128/256, TLS 1.3 + GCM 128.
- DTLS, certificate, Secure Remote Password (SRP) are not
supported yet.

Issue: #326
Signed-off-by: Sheng Mao <redacted>
---
 common/ktls.c | 702 ++++++++++++++++++++++++++++++++++++++++++++++++++
 common/ktls.h |  57 ++++
 2 files changed, 759 insertions(+)
 create mode 100644 common/ktls.c
 create mode 100644 common/ktls.h
diff --git a/common/ktls.c b/common/ktls.c
new file mode 100644
index 00000000..a4d670e3
--- /dev/null
+++ b/common/ktls.c
@@ -0,0 +1,702 @@
+/*
+ * Copyright (C) 2020 Sheng Mao.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <termios.h>
+#include <sys/stat.h>
+
+#include <linux/tls.h>
+
+#include <gnutls/gnutls.h>
+
+#include <common/utils.h>
+
+#include "ktls.h"
+
+enum { KTLS_MAX_PASSWORD_LENGTH = 256, KTLS_MAX_PRIORITY_STRING_LENTH = 256 };
+
+enum {
+	KTLS_STAGE_NOT_HANDSHAKED,
+	KTLS_STAGE_HAS_HANDSHAKED,
+};
+
+enum ktls_tls_mode_t {
+	KTLS_TLS_MODE_NONE = 0,
+	KTLS_TLS_12_128_GCM,
+	KTLS_TLS_13_128_GCM,
+	KTLS_TLS_12_256_GCM
+};
+
+struct ktls_session {
+	gnutls_session_t session;
+	gnutls_certificate_credentials_t crt_cred;
+
+	gnutls_psk_server_credentials_t psk_cred_server;
+	gnutls_psk_client_credentials_t psk_cred_client;
+
+	uint8_t role;
+	uint8_t stage;
+	enum ktls_tls_mode_t tls_mode;
+};
+
+static gnutls_datum_t ktls_psk_username = { 0 };
+static gnutls_datum_t ktls_psk_key = { 0 };
+
+static void ktls_print_logs(int level, const char *msg)
+{
+	if (bconf.verbose >= level)
+		printf("GnuTLS [%d]: %s", level, msg);
+}
+
+int ktls_set_psk_session_from_password_prompt(struct ktls_session *session,
+					      const char *username)
+{
+	struct termios orig_term_flags, passwd_term_flags;
+	char passwd[KTLS_MAX_PASSWORD_LENGTH];
+	int passwd_sz = 0;
+	int stdin_fd = 0;
+
+	if (!session)
+		return EXIT_FAILURE;
+
+	if (session->tls_mode == KTLS_TLS_MODE_NONE)
+		return EXIT_SUCCESS;
+
+	passwd[0] = '\0';
+
+	stdin_fd = fileno(stdin);
+
+	if (!isatty(stdin_fd)) {
+		error("tty needed for password input");
+		return EXIT_FAILURE;
+	}
+
+	tcgetattr(stdin_fd, &orig_term_flags);
+	passwd_term_flags = orig_term_flags;
+	passwd_term_flags.c_lflag &= ~ECHO;
+	passwd_term_flags.c_lflag |= ECHONL;
+
+	if (tcsetattr(stdin_fd, TCSANOW, &passwd_term_flags)) {
+		error("fail to hide password: %s", strerror(errno));
+		return EXIT_FAILURE;
+	}
+
+	printf("password: ");
+	if (!fgets(passwd, sizeof(passwd), stdin)) {
+		error("no password read");
+		return EXIT_FAILURE;
+	}
+
+	if (tcsetattr(fileno(stdin), TCSANOW, &orig_term_flags)) {
+		error("fail to reset tty: %s", strerror(errno));
+		return EXIT_FAILURE;
+	}
+
+	passwd_sz = strlen(passwd);
+	if (passwd_sz <= 0) {
+		error("no password read");
+		return EXIT_FAILURE;
+	}
+	if (passwd[passwd_sz - 1] == '\n') {
+		passwd[passwd_sz - 1] = '\0';
+		passwd_sz--;
+	}
+
+	return ktls_set_psk_session(session, username,
+				    (const unsigned char *)passwd, passwd_sz);
+}
+
+int ktls_set_psk_session_from_keyfile(struct ktls_session *session,
+				      const char *username,
+				      const char *key_file)
+{
+	int rc = GNUTLS_E_SUCCESS;
+	struct stat file_stat;
+	size_t sz = 0;
+	FILE *fp = NULL;
+	gnutls_datum_t input = { NULL, 0UL };
+	gnutls_datum_t output = { NULL, 0UL };
+
+	if (!session)
+		goto cleanup;
+
+	if (session->tls_mode == KTLS_TLS_MODE_NONE)
+		return EXIT_SUCCESS;
+
+	if (stat(key_file, &file_stat)) {
+		error("fail to open keyfile: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	fp = fopen(key_file, "r");
+	if (!fp) {
+		error("fail to open keyfile: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	input.size = file_stat.st_size;
+
+	input.data = gnutls_malloc(input.size);
+
+	sz = fread(input.data, 1, input.size, fp);
+
+	if (sz != input.size) {
+		error("fail to read PEM");
+		goto cleanup;
+	}
+
+	rc = gnutls_pem_base64_decode2(NULL, &input, &output);
+	if (rc != GNUTLS_E_SUCCESS) {
+		error("Error! fail to decode PEM: %s", gnutls_strerror(rc));
+		goto cleanup;
+	}
+
+	return ktls_set_psk_session(session, username, output.data,
+				    output.size);
+
+cleanup:
+	return EXIT_FAILURE;
+}
+
+struct ktls_session *ktls_create_session(bool is_sender)
+{
+	struct ktls_session *session = NULL;
+
+	session = (struct ktls_session *)malloc(sizeof(struct ktls_session));
+	explicit_bzero(session, sizeof(*session));
+
+	gnutls_global_init();
+
+	session->role = is_sender ? GNUTLS_CLIENT : GNUTLS_SERVER;
+	session->stage = KTLS_STAGE_NOT_HANDSHAKED;
+
+	gnutls_init(&session->session, session->role);
+
+	gnutls_global_set_log_level(bconf.verbose);
+	gnutls_global_set_log_function(ktls_print_logs);
+
+	return session;
+}
+
+void ktls_destroy_session(struct ktls_session *session)
+{
+	if (!session)
+		return;
+
+	if (session->crt_cred)
+		gnutls_certificate_free_credentials(session->crt_cred);
+
+	if (session->psk_cred_server)
+		gnutls_psk_free_server_credentials(session->psk_cred_server);
+
+	if (session->psk_cred_client)
+		gnutls_psk_free_client_credentials(session->psk_cred_client);
+
+	if (session->session) {
+		if (session->stage == KTLS_STAGE_HAS_HANDSHAKED)
+			gnutls_bye(session->session, GNUTLS_SHUT_RDWR);
+		gnutls_deinit(session->session);
+	}
+
+	gnutls_global_deinit();
+
+	explicit_bzero(session, sizeof(*session));
+}
+
+static int ktls_connect_or_bind(int *sock, bool is_sender, int protocol,
+				struct sockaddr *serv_addr, size_t serv_addr_sz)
+{
+	*sock = 0;
+
+	*sock = socket(protocol, SOCK_STREAM, 0);
+	if (*sock == KTLS_INVALID_FD) {
+		error("could not create socket: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	if (is_sender) {
+		if (connect(*sock, serv_addr, serv_addr_sz)) {
+			error("fail to connect to server: %s", strerror(errno));
+			goto cleanup;
+		}
+		return EXIT_SUCCESS;
+	}
+
+	if (setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 },
+		       sizeof(int))) {
+		error("fail to connect to server: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	if (bind(*sock, serv_addr, serv_addr_sz) || listen(*sock, 1)) {
+		error("fail to serve as server: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	return EXIT_SUCCESS;
+
+cleanup:
+	if (*sock >= 0)
+		close(*sock);
+	return EXIT_FAILURE;
+}
+
+static int ktls_connect_domain(int *sock, bool is_sender, const char *host,
+			       const uint16_t port)
+{
+	struct addrinfo hints = { 0 }, *res = NULL;
+	int rc = 0;
+	struct sockaddr_in addr4;
+	struct sockaddr_in6 addr6;
+
+	memset(&hints, 0, sizeof(hints));
+
+	hints.ai_family = PF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_flags |= AI_CANONNAME;
+
+	if (getaddrinfo(host, NULL, &hints, &res)) {
+		error("fail to get address info: %s", strerror(errno));
+		return EXIT_FAILURE;
+	}
+
+	while (res) {
+		switch (res->ai_family) {
+		case AF_INET:
+			explicit_bzero(&addr4, sizeof(addr4));
+			addr4.sin_addr =
+				((struct sockaddr_in *)res->ai_addr)->sin_addr;
+			addr4.sin_port = port;
+			addr4.sin_family = res->ai_family;
+			if (!ktls_connect_or_bind(
+				    sock, is_sender, res->ai_family,
+				    (struct sockaddr *)&addr4, sizeof(addr4))) {
+				goto cleanup;
+			}
+			break;
+		case AF_INET6:
+			explicit_bzero(&addr6, sizeof(addr6));
+			addr6.sin6_addr =
+				((struct sockaddr_in6 *)res->ai_addr)->sin6_addr;
+			addr6.sin6_port = port;
+			addr6.sin6_family = res->ai_family;
+			if (!ktls_connect_or_bind(
+				    sock, is_sender, res->ai_family,
+				    (struct sockaddr *)&addr6, sizeof(addr6))) {
+				goto cleanup;
+			}
+			break;
+		}
+		res = res->ai_next;
+	}
+
+	freeaddrinfo(res);
+	return EXIT_FAILURE;
+
+cleanup:
+	freeaddrinfo(res);
+	return rc;
+}
+
+static int ktls_connect_ip(int *sock, bool is_sender, const char *host,
+			   const uint16_t port)
+{
+	struct sockaddr_in addr4;
+	struct sockaddr_in6 addr6;
+	struct sockaddr *serv_addr = NULL;
+	size_t serv_addr_sz = 0;
+	sa_family_t protol = AF_INET;
+
+	explicit_bzero(&addr4, sizeof(addr4));
+	explicit_bzero(&addr6, sizeof(addr6));
+
+	if (inet_pton(AF_INET, host, &addr4.sin_addr) == 1) {
+		serv_addr = (struct sockaddr *)&addr4;
+		serv_addr_sz = sizeof(addr4);
+		protol = addr4.sin_family = AF_INET;
+		addr4.sin_port = port;
+	}
+
+	if (!serv_addr && inet_pton(AF_INET6, host, &addr6.sin6_addr) == 1) {
+		serv_addr = (struct sockaddr *)&addr6;
+		serv_addr_sz = sizeof(addr6);
+		protol = addr6.sin6_family = AF_INET6;
+		addr6.sin6_port = port;
+	}
+
+	if (!serv_addr)
+		return KTLS_INVALID_FD;
+
+	return ktls_connect_or_bind(sock, is_sender, protol, serv_addr,
+				    serv_addr_sz);
+}
+
+int ktls_set_tls_mode(struct ktls_session *session, const char *mode)
+{
+	if (!session)
+		return EXIT_FAILURE;
+
+	if (!strcmp("none", mode))
+		session->tls_mode = KTLS_TLS_MODE_NONE;
+	else if (!strcmp("tls_12_128_gcm", mode))
+		session->tls_mode = KTLS_TLS_12_128_GCM;
+	else if (!strcmp("tls_13_128_gcm", mode))
+		session->tls_mode = KTLS_TLS_13_128_GCM;
+	else if (!strcmp("tls_12_256_gcm", mode))
+		session->tls_mode = KTLS_TLS_12_256_GCM;
+	else {
+		error("unknown tls mode: %s", mode);
+		return EXIT_FAILURE;
+	}
+	return EXIT_SUCCESS;
+}
+
+#define INIT_GCM_WITH_MODE(V, X)                                               \
+	{                                                                      \
+		struct tls12_crypto_info_aes_gcm_##X crypto_info;              \
+\
+		crypto_info.info.version = TLS_##V##_VERSION;                  \
+		crypto_info.info.cipher_type = TLS_CIPHER_AES_GCM_##X;         \
+		memcpy(crypto_info.iv, seq_number,                             \
+		       TLS_CIPHER_AES_GCM_##X##_IV_SIZE);                      \
+		memcpy(crypto_info.rec_seq, seq_number,                        \
+		       TLS_CIPHER_AES_GCM_##X##_REC_SEQ_SIZE);                 \
+		if (cipher_key.size != TLS_CIPHER_AES_GCM_##X##_KEY_SIZE) {    \
+			error("mismatch in send key size: %d != %d\n",         \
+			      cipher_key.size,                                 \
+			      TLS_CIPHER_AES_GCM_##X##_KEY_SIZE);              \
+			goto cleanup;                                          \
+		}                                                              \
+		memcpy(crypto_info.key, cipher_key.data,                       \
+		       TLS_CIPHER_AES_GCM_##X##_KEY_SIZE);                     \
+		memcpy(crypto_info.salt, iv.data,                              \
+		       TLS_CIPHER_AES_GCM_##X##_SALT_SIZE);                    \
+		if (setsockopt(sock, SOL_TLS, is_sender ? TLS_TX : TLS_RX,     \
+			       &crypto_info, sizeof(crypto_info))) {           \
+			error("fail to set kernel tls: %s", strerror(errno));  \
+			goto cleanup;                                          \
+		}                                                              \
+	}
+
+int ktls_handshake_tls(struct ktls_session *session, int sock)
+{
+	int rc = 0;
+	bool is_sender = false;
+	int handshake_retry = 3;
+	char tls_priority_list[KTLS_MAX_PRIORITY_STRING_LENTH];
+	const char *tls_priority_templ =
+		"NONE:+MAC-ALL:+COMP-NULL:+SIGN-ALL:+GROUP-ALL:+ECDHE-PSK:+DHE-PSK:%s:%s";
+	const char *tls_priority_ver_mode = NULL;
+
+	gnutls_datum_t mac_key;
+	gnutls_datum_t iv;
+	gnutls_datum_t cipher_key;
+	unsigned char seq_number[8];
+
+	if (!session || !session->session)
+		return EXIT_FAILURE;
+
+	switch (session->tls_mode) {
+	case KTLS_TLS_MODE_NONE:
+		return EXIT_SUCCESS;
+	case KTLS_TLS_12_128_GCM:
+		tls_priority_ver_mode = "+VERS-TLS1.2:+AES-128-GCM";
+		break;
+	case KTLS_TLS_13_128_GCM:
+		tls_priority_ver_mode = "+VERS-TLS1.3:+AES-128-GCM";
+		break;
+	case KTLS_TLS_12_256_GCM:
+		tls_priority_ver_mode = "+VERS-TLS1.2:+AES-256-GCM";
+		break;
+	default:
+		error("unknown tls mode");
+		goto cleanup;
+	}
+
+	is_sender = session->role == GNUTLS_CLIENT;
+
+	if (is_sender && session->psk_cred_client) {
+		rc = gnutls_credentials_set(session->session, GNUTLS_CRD_PSK,
+					    session->psk_cred_client);
+		if (rc != GNUTLS_E_SUCCESS) {
+			error("fail to set PSK for client: %s",
+			      gnutls_strerror(rc));
+			goto cleanup;
+		}
+	}
+
+	if (!is_sender && session->psk_cred_server) {
+		rc = gnutls_credentials_set(session->session, GNUTLS_CRD_PSK,
+					    session->psk_cred_server);
+		if (rc != GNUTLS_E_SUCCESS) {
+			error("fail to set PSK for server: %s",
+			      gnutls_strerror(rc));
+			goto cleanup;
+		}
+	}
+
+	if (session->crt_cred) {
+		rc = gnutls_credentials_set(session->session,
+					    GNUTLS_CRD_CERTIFICATE,
+					    session->crt_cred);
+
+		if (rc == GNUTLS_E_SUCCESS) {
+			error("fail to set certificate: %s",
+			      gnutls_strerror(rc));
+			goto cleanup;
+		}
+	}
+
+	snprintf(tls_priority_list, KTLS_MAX_PRIORITY_STRING_LENTH,
+		 tls_priority_templ,
+		 is_sender ? "+CTYPE-CLI-ALL" : "+CTYPE-SRV-ALL",
+		 tls_priority_ver_mode);
+
+	rc = gnutls_priority_set_direct(session->session, tls_priority_list,
+					NULL);
+	if (rc != GNUTLS_E_SUCCESS) {
+		error("fail to set priority: %s", gnutls_strerror(rc));
+		goto cleanup;
+	}
+
+	gnutls_transport_set_int(session->session, sock);
+
+	gnutls_handshake_set_timeout(session->session,
+				     GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+
+	do {
+		if (handshake_retry < 0) {
+			error("exhaust retries on handshake");
+			break;
+		}
+		rc = gnutls_handshake(session->session);
+		handshake_retry--;
+	} while (rc < 0 && !gnutls_error_is_fatal(rc));
+
+	if (gnutls_error_is_fatal(rc)) {
+		error("fail on handshake: %s", gnutls_strerror(rc));
+		goto cleanup;
+	}
+	if (bconf.verbose > 0) {
+		char *desc = gnutls_session_get_desc(session->session);
+
+		printf("TLS session info: %s\n", desc);
+		gnutls_free(desc);
+	}
+
+	session->stage = KTLS_STAGE_HAS_HANDSHAKED;
+
+	rc = gnutls_record_get_state(session->session, is_sender ? 0 : 1,
+				     &mac_key, &iv, &cipher_key, seq_number);
+	if (rc != GNUTLS_E_SUCCESS) {
+		error("fail on retrieve TLS record: %s", gnutls_strerror(rc));
+		goto cleanup;
+	}
+
+	if (setsockopt(sock, SOL_TCP, TCP_ULP, "tls", sizeof("tls"))) {
+		error("fail to set kernel TLS on socket: %s", strerror(errno));
+		goto cleanup;
+	}
+
+	switch (session->tls_mode) {
+	case KTLS_TLS_12_128_GCM:
+		INIT_GCM_WITH_MODE(1_2, 128);
+		break;
+	case KTLS_TLS_13_128_GCM:
+		INIT_GCM_WITH_MODE(1_3, 128);
+		break;
+	case KTLS_TLS_12_256_GCM:
+		INIT_GCM_WITH_MODE(1_2, 256);
+		break;
+	default:
+		error("unknown tls mode");
+		goto cleanup;
+	}
+
+	if (bconf.verbose > 0)
+		fprintf(stderr, "ktls init done\n");
+
+	return EXIT_SUCCESS;
+
+cleanup:
+	return EXIT_FAILURE;
+}
+
+static int ktls_cp_datum(gnutls_datum_t *to, const gnutls_datum_t *from)
+{
+	if (!to || !from)
+		return EXIT_FAILURE;
+
+	to->size = from->size;
+	to->data = (unsigned char *)gnutls_malloc(to->size);
+	memmove(to->data, from->data, to->size);
+
+	return EXIT_SUCCESS;
+}
+
+static int ktls_cmp_datum(const gnutls_datum_t *lhs, const gnutls_datum_t *rhs)
+{
+	if (!lhs && !rhs)
+		return EXIT_SUCCESS;
+
+	if (!lhs || !rhs)
+		return EXIT_FAILURE;
+
+	if (lhs->size != rhs->size)
+		return EXIT_FAILURE;
+
+	return memcmp(lhs->data, rhs->data, lhs->size);
+}
+
+static int ktls_set_datum(gnutls_datum_t *to, const unsigned char *from,
+			  int from_size)
+{
+	if (!to || !from || !from_size)
+		return EXIT_FAILURE;
+
+	if (from_size < 0)
+		from_size = strlen((const char *)from);
+
+	to->size = from_size;
+	to->data = (unsigned char *)gnutls_malloc(to->size);
+	memmove(to->data, from, from_size);
+
+	return EXIT_SUCCESS;
+}
+
+static int tls_psk_client_callback(gnutls_session_t session,
+				   gnutls_datum_t *username,
+				   gnutls_datum_t *key)
+{
+	if (ktls_cp_datum(username, &ktls_psk_username) ||
+	    ktls_cp_datum(key, &ktls_psk_key))
+		return EXIT_FAILURE;
+
+	return EXIT_SUCCESS;
+}
+
+static int tls_psk_server_callback(gnutls_session_t session,
+				   const gnutls_datum_t *username,
+				   gnutls_datum_t *key)
+{
+	if (ktls_cmp_datum(username, &ktls_psk_username) ||
+	    ktls_cp_datum(key, &ktls_psk_key))
+		return EXIT_FAILURE;
+
+	return EXIT_SUCCESS;
+}
+
+int ktls_set_psk_session(struct ktls_session *session, const char *username,
+			 const unsigned char *passwd, const size_t sz_passwd)
+{
+	bool is_sender = false;
+	int rc = 0;
+
+	if (!session || !session->session)
+		goto cleanup;
+
+	is_sender = session->role == GNUTLS_CLIENT;
+
+	if (!is_sender && !session->psk_cred_server) {
+		rc = gnutls_psk_allocate_server_credentials(
+			&session->psk_cred_server);
+		if (rc != GNUTLS_E_SUCCESS) {
+			error("fail on set psk for server: %s",
+			      gnutls_strerror(rc));
+			goto cleanup;
+		}
+		gnutls_psk_set_server_credentials_function2(
+			session->psk_cred_server, tls_psk_server_callback);
+	}
+
+	if (is_sender && !session->psk_cred_client) {
+		rc = gnutls_psk_allocate_client_credentials(
+			&session->psk_cred_client);
+		if (rc != GNUTLS_E_SUCCESS) {
+			error("fail on set psk for client: %s",
+			      gnutls_strerror(rc));
+			goto cleanup;
+		}
+		gnutls_psk_set_client_credentials_function2(
+			session->psk_cred_client, tls_psk_client_callback);
+	}
+
+	if (!ktls_psk_key.size)
+		gnutls_free(ktls_psk_key.data);
+
+	if (!ktls_psk_username.size)
+		gnutls_free(ktls_psk_username.data);
+
+	if (ktls_set_datum(&ktls_psk_username, (const unsigned char *)username,
+			   -1) ||
+	    ktls_set_datum(&ktls_psk_key, passwd, sz_passwd))
+		goto cleanup;
+
+	return EXIT_SUCCESS;
+
+cleanup:
+	return EXIT_FAILURE;
+}
+
+int ktls_create_sock_oneshot(struct ktls_session *session, const char *host,
+			     const char *port)
+{
+	int sock = 0;
+	int nport = 0;
+	bool is_sender;
+
+	if (!session || !session->session)
+		return EXIT_FAILURE;
+
+	is_sender = session->role == GNUTLS_CLIENT;
+
+	nport = atoi(port);
+
+	if (nport >= 0 && nport <= 65535)
+		nport = htons((uint16_t)nport);
+
+	if (ktls_connect_ip(&sock, is_sender, host, (uint16_t)nport))
+		if (ktls_connect_domain(&sock, is_sender, host, nport))
+			goto cleanup;
+
+	if (!is_sender) {
+		int accepted_sock = KTLS_INVALID_FD;
+
+		accepted_sock = accept(sock, (struct sockaddr *)NULL, NULL);
+		close(sock);
+		sock = accepted_sock;
+	}
+
+	if (ktls_handshake_tls(session, sock))
+		goto cleanup;
+
+	return sock;
+
+cleanup:
+	close(sock);
+	return KTLS_INVALID_FD;
+}
diff --git a/common/ktls.h b/common/ktls.h
new file mode 100644
index 00000000..d744e18e
--- /dev/null
+++ b/common/ktls.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 Sheng Mao.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License v2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef __BTRFS_KTLS_H__
+#define __BTRFS_KTLS_H__
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ktls_session;
+
+enum { KTLS_INVALID_FD = -1 };
+
+struct ktls_session *ktls_create_session(bool is_sender);
+void ktls_destroy_session(struct ktls_session *session);
+
+// ktls_set_psk_session sets PSK (pre-shared key). username is NULL-terminated
+// string; passwd is sized string. Memory of both strings are managed by
+// caller. currently, this API only allows to set PSK before calling
+// ktls_handshake_*()
+int ktls_set_psk_session(struct ktls_session *session, const char *username,
+			 const unsigned char *passwd, const size_t sz_passwd);
+
+int ktls_set_psk_session_from_password_prompt(struct ktls_session *session,
+					      const char *username);
+
+int ktls_set_psk_session_from_keyfile(struct ktls_session *session,
+				      const char *username,
+				      const char *key_file);
+
+int ktls_set_tls_mode(struct ktls_session *session, const char *mode);
+
+int ktls_handshake_tls(struct ktls_session *session, int sock);
+
+// ktls_create_sock_oneshot returns a sock fd on success.
+int ktls_create_sock_oneshot(struct ktls_session *session, const char *host,
+			     const char *port);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __BTRFS_KTLS_H__
-- 
2.29.2
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help