diff --git a/Documentation/btrfs-receive.asciidoc b/Documentation/btrfs-receive.asciidoc
index e4c4d2c0..0bc70165 100644
--- a/Documentation/btrfs-receive.asciidoc
+++ b/Documentation/btrfs-receive.asciidoc
@@ -38,6 +38,19 @@ A subvolume is made read-only after the receiving process finishes successfully
 -f <FILE>::
 read the stream from <FILE> instead of stdin,
 
+--tls-addr <url>::
+Address to listen on. It can be an IP address or a domain name.
+
+--tls-port <port>::
+The local port of the TLS connection.
+
+--tls-key <file>::
+Use the key from file; otherwise read key from stdin. Key file is first parsed
+as PEM format; if parsing fails, file content is treated as binary key.
+
+--tls-mode <mode>::
+Use tls_12_128_gcm, tls_13_128_gcm, tls_12_256_gcm.
+
 -C|--chroot::
 confine the process to 'path' using `chroot`(1)
 
diff --git a/Documentation/btrfs-send.asciidoc b/Documentation/btrfs-send.asciidoc
index c4a05672..313451d5 100644
--- a/Documentation/btrfs-send.asciidoc
+++ b/Documentation/btrfs-send.asciidoc
@@ -49,6 +49,15 @@ use this snapshot as a clone source for an incremental send (multiple allowed)
 -f <outfile>::
 output is normally written to standard output so it can be, for example, piped
 to btrfs receive. Use this option to write it to a file instead.
+--tls-addr <url>::
+Address of remote receiver. It can be an IP address or a domain name.
+--tls-port <port>::
+The remote port of the TLS connection.
+--tls-key <file>::
+Use the key from file; otherwise read key from stdin. Key file is first parsed
+as PEM format; if parsing fails, file content is treated as binary key.
+--tls-mode <mode>::
+Use tls_12_128_gcm, tls_13_128_gcm, tls_12_256_gcm.
 --no-data::
 send in 'NO_FILE_DATA' mode
 +
diff --git a/cmds/receive.c b/cmds/receive.c
index 2aaba3ff..e1dc5415 100644
--- a/cmds/receive.c
+++ b/cmds/receive.c
@@ -53,8 +53,11 @@
 #include "common/help.h"
 #include "common/path-utils.h"
 
-struct btrfs_receive
-{
+#if KTLS_SEND_RECV == 1
+#include "common/ktls.h"
+#endif
+
+struct btrfs_receive {
 	int mnt_fd;
 	int dest_dir_fd;
 
@@ -1216,7 +1219,7 @@ out:
 	return ret;
 }
 
-static const char * const cmd_receive_usage[] = {
+static const char *const cmd_receive_usage[] = {
 	"btrfs receive [options] <mount>\n"
 	"btrfs receive --dump [options]",
 	"Receive subvolumes from a stream",
@@ -1229,22 +1232,28 @@ static const char * const cmd_receive_usage[] = {
 	"After receiving a subvolume, it is immediately set to",
 	"read-only.",
 	"",
-	"-q|--quiet       suppress all messages, except errors",
-	"-f FILE          read the stream from FILE instead of stdin",
-	"-e               terminate after receiving an <end cmd> marker in the stream.",
-	"                 Without this option the receiver side terminates only in case",
-	"                 of an error on end of file.",
-	"-C|--chroot      confine the process to <mount> using chroot",
+	"-q|--quiet        suppress all messages, except errors",
+	"-f FILE           read the stream from FILE instead of stdin",
+#if KTLS_SEND_RECV == 1
+	"--tls-addr <url>      Address to listen on for incoming TLS connection.",
+	"--tls-port <port>     The remote port of the TLS connection",
+	"--tls-key <file>      Use the key from file; otherwise read key from stdin.",
+	"--tls-mode <mode> Use tls_12_128_gcm, tls_13_128_gcm, tls_12_256_gcm."
+#endif
+	"-e                terminate after receiving an <end cmd> marker in the stream.",
+	"                  Without this option the receiver side terminates only in case",
+	"                  of an error on end of file.",
+	"-C|--chroot       confine the process to <mount> using chroot",
 	"-E|--max-errors NERR",
-	"                 terminate as soon as NERR errors occur while",
-	"                 stream processing commands from the stream.",
-	"                 Default value is 1. A value of 0 means no limit.",
-	"-m ROOTMOUNT     the root mount point of the destination filesystem.",
-	"                 If /proc is not accessible, use this to tell us where",
-	"                 this file system is mounted.",
-	"--dump           dump stream metadata, one line per operation,",
-	"                 does not require the MOUNT parameter",
-	"-v               deprecated, alias for global -v option",
+	"                  terminate as soon as NERR errors occur while",
+	"                  stream processing commands from the stream.",
+	"                  Default value is 1. A value of 0 means no limit.",
+	"-m ROOTMOUNT      the root mount point of the destination filesystem.",
+	"                  If /proc is not accessible, use this to tell us where",
+	"                  this file system is mounted.",
+	"--dump            dump stream metadata, one line per operation,",
+	"                  does not require the MOUNT parameter",
+	"-v                deprecated, alias for global -v option",
 	HELPINFO_INSERT_GLOBALS,
 	HELPINFO_INSERT_VERBOSE,
 	HELPINFO_INSERT_QUIET,
@@ -1261,6 +1270,23 @@ static int cmd_receive(const struct cmd_struct *cmd, int argc, char **argv)
 	u64 max_errors = 1;
 	int dump = 0;
 	int ret = 0;
+#if KTLS_SEND_RECV == 1
+	enum {
+		KTLS_IDX_ADDR = 0,
+		KTLS_IDX_PORT = 1,
+		KTLS_IDX_KEY = 2,
+		KTLS_IDX_TLS_MODE = 3,
+		KTLS_IDX_SIZE
+	};
+	char *ktls_args[KTLS_IDX_SIZE];
+	struct ktls_session *ktls_session = NULL;
+	char ktls_username[LOGIN_NAME_MAX] = "btrfs";
+	u32 i = 0;
+	size_t arg_len = 0;
+	int arg_idx = 0;
+
+	explicit_bzero(ktls_args, sizeof(ktls_args));
+#endif
 
 	memset(&rctx, 0, sizeof(rctx));
 	rctx.mnt_fd = -1;
@@ -1285,10 +1311,25 @@ static int cmd_receive(const struct cmd_struct *cmd, int argc, char **argv)
 	optind = 0;
 	while (1) {
 		int c;
-		enum { GETOPT_VAL_DUMP = 257 };
+		enum {
+			GETOPT_VAL_DUMP = 257,
+			GETOPT_VAL_ADDR = 300,
+			GETOPT_VAL_PORT = 301,
+			GETOPT_VAL_KEY = 302,
+			GETOPT_VAL_TLS_MODE = 303,
+		};
 		static const struct option long_opts[] = {
 			{ "max-errors", required_argument, NULL, 'E' },
 			{ "chroot", no_argument, NULL, 'C' },
+#if KTLS_SEND_RECV == 1
+			{ "tls-addr", required_argument, NULL,
+			  GETOPT_VAL_ADDR },
+			{ "tls-port", required_argument, NULL,
+			  GETOPT_VAL_PORT },
+			{ "tls-key", required_argument, NULL, GETOPT_VAL_KEY },
+			{ "tls-mode", required_argument, NULL,
+			  GETOPT_VAL_TLS_MODE },
+#endif
 			{ "dump", no_argument, NULL, GETOPT_VAL_DUMP },
 			{ "quiet", no_argument, NULL, 'q' },
 			{ NULL, 0, NULL, 0 }
@@ -1330,6 +1371,27 @@ static int cmd_receive(const struct cmd_struct *cmd, int argc, char **argv)
 				goto out;
 			}
 			break;
+#if KTLS_SEND_RECV == 1
+		case GETOPT_VAL_ADDR:
+		case GETOPT_VAL_PORT:
+		case GETOPT_VAL_KEY:
+		case GETOPT_VAL_TLS_MODE:
+			arg_len = strlen(optarg);
+			arg_idx = c - GETOPT_VAL_ADDR;
+			ktls_args[arg_idx] = (char *)malloc(arg_len + 1);
+			if (!ktls_args[arg_idx]) {
+				error("fail to allocate buffer (%zu)", arg_len);
+				ret = 1;
+				goto out;
+			}
+			if (arg_copy_path(ktls_args[arg_idx], optarg,
+					  arg_len + 1)) {
+				error("argument too long (%zu)", arg_len);
+				ret = 1;
+				goto out;
+			}
+			break;
+#endif
 		case GETOPT_VAL_DUMP:
 			dump = 1;
 			break;
@@ -1353,6 +1415,50 @@ static int cmd_receive(const struct cmd_struct *cmd, int argc, char **argv)
 		}
 	}
 
+#if KTLS_SEND_RECV == 1
+	if (ktls_args[KTLS_IDX_ADDR]) {
+		if (fromfile[0]) {
+			error("cannot send to both ktls and file");
+			ret = 1;
+			goto out;
+		}
+
+		if (!ktls_args[KTLS_IDX_PORT]) {
+			error("no remote ktls port");
+			ret = 1;
+			goto out;
+		}
+
+		ktls_session = ktls_create_session(false);
+
+		if (ktls_args[KTLS_IDX_TLS_MODE]) {
+			if (ktls_set_tls_mode(ktls_session,
+					      ktls_args[KTLS_IDX_TLS_MODE]))
+				goto out;
+		}
+
+		if (ktls_args[KTLS_IDX_KEY]) {
+			if (ktls_set_psk_session_from_keyfile(
+				    ktls_session, ktls_username,
+				    ktls_args[KTLS_IDX_KEY])) {
+				goto out;
+			};
+		} else {
+			if (ktls_set_psk_session_from_password_prompt(
+				    ktls_session, ktls_username)) {
+				goto out;
+			}
+		}
+
+		receive_fd = ktls_create_sock_oneshot(ktls_session,
+						      ktls_args[KTLS_IDX_ADDR],
+						      ktls_args[KTLS_IDX_PORT]);
+
+		/* socket implies honor end cmd*/
+		rctx.honor_end_cmd = 1;
+	}
+#endif
+
 	if (dump) {
 		struct btrfs_dump_send_args dump_args;
 
@@ -1370,9 +1476,16 @@ static int cmd_receive(const struct cmd_struct *cmd, int argc, char **argv)
 		ret = do_receive(&rctx, tomnt, realmnt, receive_fd, max_errors);
 	}
 
+out:
+#if KTLS_SEND_RECV == 1
+	for (i = KTLS_IDX_ADDR; i < KTLS_IDX_SIZE; i++) {
+		if (ktls_args[i])
+			free(ktls_args[i]);
+	}
+	ktls_destroy_session(ktls_session);
+#endif
 	if (receive_fd != fileno(stdin))
 		close(receive_fd);
-out:
 
 	return !!ret;
 }
diff --git a/cmds/send.c b/cmds/send.c
index b8e3ba12..2b58b0c2 100644
--- a/cmds/send.c
+++ b/cmds/send.c
@@ -46,6 +46,10 @@
 #include "common/help.h"
 #include "common/path-utils.h"
 
+#if KTLS_SEND_RECV == 1
+#include "common/ktls.h"
+#endif
+
 #define SEND_BUFFER_SIZE	SZ_64K
 
 
@@ -424,7 +428,7 @@ static void free_send_info(struct btrfs_send *sctx)
 	subvol_uuid_search_finit(&sctx->sus);
 }
 
-static const char * const cmd_send_usage[] = {
+static const char *const cmd_send_usage[] = {
 	"btrfs send [-ve] [-p <parent>] [-c <clone-src>] [-f <outfile>] <subvol> [<subvol>...]",
 	"Send the subvolume(s) to stdout.",
 	"Sends the subvolume(s) specified by <subvol> to stdout.",
@@ -439,21 +443,27 @@ static const char * const cmd_send_usage[] = {
 	"which case 'btrfs send' will determine a suitable parent among the",
 	"clone sources itself.",
 	"",
-	"-e               If sending multiple subvols at once, use the new",
-	"                 format and omit the end-cmd between the subvols.",
-	"-p <parent>      Send an incremental stream from <parent> to",
-	"                 <subvol>.",
-	"-c <clone-src>   Use this snapshot as a clone source for an ",
-	"                 incremental send (multiple allowed)",
-	"-f <outfile>     Output is normally written to stdout. To write to",
-	"                 a file, use this option. An alternative would be to",
-	"                 use pipes.",
-	"--no-data        send in NO_FILE_DATA mode, Note: the output stream",
-	"                 does not contain any file data and thus cannot be used",
-	"                 to transfer changes. This mode is faster and useful to",
-	"                 show the differences in metadata.",
-	"-v|--verbose     deprecated, alias for global -v option",
-	"-q|--quiet       deprecated, alias for global -q option",
+	"-e                If sending multiple subvols at once, use the new",
+	"                  format and omit the end-cmd between the subvols.",
+	"-p <parent>       Send an incremental stream from <parent> to",
+	"                  <subvol>.",
+	"-c <clone-src>    Use this snapshot as a clone source for an ",
+	"                  incremental send (multiple allowed)",
+	"-f <outfile>      Output is normally written to stdout. To write to",
+	"                  a file, use this option. An alternative would be to",
+	"                  use pipes.",
+#if KTLS_SEND_RECV == 1
+	"--tls-addr <url>      Address of remote receiver.",
+	"--tls-port <port>     The remote port of the TLS connection",
+	"--tls-key <file>      Use the key from file; otherwise read key from stdin.",
+	"--tls-mode <mode> Use tls_12_128_gcm, tls_13_128_gcm, tls_12_256_gcm."
+#endif
+	"--no-data         send in NO_FILE_DATA mode, Note: the output stream",
+	"                  does not contain any file data and thus cannot be used",
+	"                  to transfer changes. This mode is faster and useful to",
+	"                  show the differences in metadata.",
+	"-v|--verbose      deprecated, alias for global -v option",
+	"-q|--quiet        deprecated, alias for global -q option",
 	HELPINFO_INSERT_GLOBALS,
 	HELPINFO_INSERT_VERBOSE,
 	HELPINFO_INSERT_QUIET,
@@ -474,6 +484,22 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
 	int full_send = 1;
 	int new_end_cmd_semantic = 0;
 	u64 send_flags = 0;
+#if KTLS_SEND_RECV == 1
+	enum {
+		KTLS_IDX_ADDR = 0,
+		KTLS_IDX_PORT = 1,
+		KTLS_IDX_KEY = 2,
+		KTLS_IDX_TLS_MODE = 3,
+		KTLS_IDX_SIZE
+	};
+	char *ktls_args[KTLS_IDX_SIZE];
+	struct ktls_session *ktls_session = NULL;
+	char ktls_username[LOGIN_NAME_MAX] = "btrfs";
+	size_t arg_len = 0;
+	int arg_idx = 0;
+
+	explicit_bzero(ktls_args, sizeof(ktls_args));
+#endif
 
 	memset(&send, 0, sizeof(send));
 	send.dump_fd = fileno(stdout);
@@ -492,11 +518,26 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
 
 	optind = 0;
 	while (1) {
-		enum { GETOPT_VAL_SEND_NO_DATA = 256 };
+		enum {
+			GETOPT_VAL_SEND_NO_DATA = 256,
+			GETOPT_VAL_ADDR = 300,
+			GETOPT_VAL_PORT = 301,
+			GETOPT_VAL_KEY = 302,
+			GETOPT_VAL_TLS_MODE = 303,
+		};
 		static const struct option long_options[] = {
 			{ "verbose", no_argument, NULL, 'v' },
 			{ "quiet", no_argument, NULL, 'q' },
-			{ "no-data", no_argument, NULL, GETOPT_VAL_SEND_NO_DATA }
+			{ "no-data", no_argument, NULL,
+			  GETOPT_VAL_SEND_NO_DATA },
+			{ "tls-addr", required_argument, NULL,
+			  GETOPT_VAL_ADDR },
+			{ "tls-port", required_argument, NULL,
+			  GETOPT_VAL_PORT },
+			{ "tls-key", required_argument, NULL, GETOPT_VAL_KEY },
+			{ "tls-mode", required_argument, NULL,
+			  GETOPT_VAL_TLS_MODE },
+			{ NULL, 0, NULL, 0  }
 		};
 		int c = getopt_long(argc, argv, "vqec:f:i:p:", long_options, NULL);
 
@@ -581,6 +622,27 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
 			error("option -i was removed, use -c instead");
 			ret = 1;
 			goto out;
+#if KTLS_SEND_RECV == 1
+		case GETOPT_VAL_ADDR:
+		case GETOPT_VAL_PORT:
+		case GETOPT_VAL_KEY:
+		case GETOPT_VAL_TLS_MODE:
+			arg_len = strlen(optarg);
+			arg_idx = c - GETOPT_VAL_ADDR;
+			ktls_args[arg_idx] = (char *)malloc(arg_len + 1);
+			if (!ktls_args[arg_idx]) {
+				error("fail to allocate buffer (%zu)", arg_len);
+				ret = 1;
+				goto out;
+			}
+			if (arg_copy_path(ktls_args[arg_idx], optarg,
+					  arg_len + 1)) {
+				error("argument too long (%zu)", arg_len);
+				ret = 1;
+				goto out;
+			}
+			break;
+#endif
 		case GETOPT_VAL_SEND_NO_DATA:
 			send_flags |= BTRFS_SEND_FLAG_NO_FILE_DATA;
 			break;
@@ -613,6 +675,53 @@ static int cmd_send(const struct cmd_struct *cmd, int argc, char **argv)
 			goto out;
 		}
 	}
+#if KTLS_SEND_RECV == 1
+	if (ktls_args[KTLS_IDX_ADDR]) {
+		if (outname[0]) {
+			error("cannot send to both ktls and file");
+			ret = 1;
+			goto out;
+		}
+
+		if (!ktls_args[KTLS_IDX_PORT]) {
+			error("no remote ktls port");
+			ret = 1;
+			goto out;
+		}
+
+		if (!ktls_args[KTLS_IDX_PORT]) {
+			error("fail to create ktls session");
+			ret = 1;
+			goto out;
+		}
+
+		ktls_session = ktls_create_session(true);
+
+		if (ktls_args[KTLS_IDX_TLS_MODE]) {
+			if (ktls_set_tls_mode(ktls_session,
+					      ktls_args[KTLS_IDX_TLS_MODE]))
+				goto out;
+		}
+
+		if (ktls_args[KTLS_IDX_KEY]) {
+			if (ktls_set_psk_session_from_keyfile(
+				    ktls_session, ktls_username,
+				    ktls_args[KTLS_IDX_KEY])) {
+				goto out;
+			};
+		} else {
+			if (ktls_set_psk_session_from_password_prompt(
+				    ktls_session, ktls_username)) {
+				goto out;
+			}
+		}
+
+		send.dump_fd =
+			ktls_create_sock_oneshot(ktls_session,
+						 ktls_args[KTLS_IDX_ADDR],
+						 ktls_args[KTLS_IDX_PORT]);
+	}
+#endif
 
 	if (isatty(send.dump_fd)) {
 		error(
@@ -755,6 +864,16 @@ out:
 	free(snapshot_parent);
 	free(send.clone_sources);
 	free_send_info(&send);
+#if KTLS_SEND_RECV == 1
+	for (i = KTLS_IDX_ADDR; i < KTLS_IDX_SIZE; i++) {
+		if (ktls_args[i])
+			free(ktls_args[i]);
+	}
+	ktls_destroy_session(ktls_session);
+#endif
+	if (send.dump_fd != fileno(stdin))
+		close(send.dump_fd);
+
 	return !!ret;
 }
 DEFINE_SIMPLE_COMMAND(send, "send");
