diff --git a/include/net/tls.h b/include/net/tls.h
index 154949c7b0c8..5156c3a80faa 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -512,4 +512,20 @@ static inline bool tls_is_sk_rx_device_offloaded(struct sock *sk)
 	return tls_get_ctx(sk)->rx_conf == TLS_HW;
 }
 #endif
+
+#define TLS_DEFAULT_PRIORITIES		(NULL)
+
+int tls_client_hello_anon(struct socket *sock,
+			  void (*done)(void *data, int status), void *data,
+			  const char *priorities);
+int tls_client_hello_x509(struct socket *sock,
+			  void (*done)(void *data, int status), void *data,
+			  const char *priorities, key_serial_t cert,
+			  key_serial_t privkey);
+int tls_client_hello_psk(struct socket *sock,
+			 void (*done)(void *data, int status), void *data,
+			 const char *priorities, key_serial_t peerid);
+int tls_server_hello(struct socket *sock, void (*done)(void *data, int status),
+		     void *data, const char *priorities);
+
 #endif /* _TLS_OFFLOAD_H */
diff --git a/include/uapi/linux/handshake.h b/include/uapi/linux/handshake.h
index 39cab687eece..54d926a49ee0 100644
--- a/include/uapi/linux/handshake.h
+++ b/include/uapi/linux/handshake.h
@@ -19,6 +19,7 @@
 /* Multicast Netlink socket groups */
 enum handshake_nlgrps {
 	HANDSHAKE_NLGRP_NONE = 0,
+	HANDSHAKE_NLGRP_TLS_13,
 	__HANDSHAKE_NLGRP_MAX
 };
 #define HSNLGRP_MAX	(__HANDSHAKE_NLGRP_MAX - 1)
@@ -40,6 +41,15 @@ enum handshake_nl_attrs {
 	HANDSHAKE_NL_ATTR_ACCEPT_RESP,
 	HANDSHAKE_NL_ATTR_DONE_ARGS,
 
+	HANDSHAKE_NL_ATTR_TLS_TYPE = 20,
+	HANDSHAKE_NL_ATTR_TLS_AUTH,
+	HANDSHAKE_NL_ATTR_TLS_PRIORITIES,
+	HANDSHAKE_NL_ATTR_TLS_X509_CERT,
+	HANDSHAKE_NL_ATTR_TLS_X509_PRIVKEY,
+	HANDSHAKE_NL_ATTR_TLS_PSK,
+	HANDSHAKE_NL_ATTR_TLS_SESS_STATUS,
+	HANDSHAKE_NL_ATTR_TLS_PEERID,
+
 	__HANDSHAKE_NL_ATTR_MAX
 };
 #define HANDSHAKE_NL_ATTR_MAX	(__HANDSHAKE_NL_ATTR_MAX - 1)
@@ -54,6 +64,26 @@ enum handshake_nl_status {
 
 enum handshake_nl_protocol {
 	HANDSHAKE_NL_PROTO_UNSPEC = 0,
+	HANDSHAKE_NL_PROTO_TLS_13,
+};
+
+enum handshake_nl_tls_type {
+	HANDSHAKE_NL_TLS_TYPE_UNSPEC = 0,
+	HANDSHAKE_NL_TLS_TYPE_CLIENTHELLO,
+	HANDSHAKE_NL_TLS_TYPE_SERVERHELLO,
+};
+
+enum handshake_nl_tls_auth {
+	HANDSHAKE_NL_TLS_AUTH_UNSPEC = 0,
+	HANDSHAKE_NL_TLS_AUTH_UNAUTH,
+	HANDSHAKE_NL_TLS_AUTH_X509,
+	HANDSHAKE_NL_TLS_AUTH_PSK,
+};
+
+enum {
+	HANDSHAKE_NO_PEERID = 0,
+	HANDSHAKE_NO_CERT = 0,
+	HANDSHAKE_NO_PRIVKEY = 0,
 };
 
 enum handshake_nl_tls_session_status {
diff --git a/net/tls/Makefile b/net/tls/Makefile
index e41c800489ac..7e56b57f14f6 100644
--- a/net/tls/Makefile
+++ b/net/tls/Makefile
@@ -7,7 +7,7 @@ CFLAGS_trace.o := -I$(src)
 
 obj-$(CONFIG_TLS) += tls.o
 
-tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o
+tls-y := tls_handshake.o tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o
 
 tls-$(CONFIG_TLS_TOE) += tls_toe.o
 tls-$(CONFIG_TLS_DEVICE) += tls_device.o tls_device_fallback.o
diff --git a/net/tls/tls_handshake.c b/net/tls/tls_handshake.c
new file mode 100644
index 000000000000..18fcea1513b0
--- /dev/null
+++ b/net/tls/tls_handshake.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Establish a TLS session for a kernel socket consumer
+ *
+ * Author: Chuck Lever <chuck.lever@oracle.com>
+ *
+ * Copyright (c) 2021-2023, Oracle and/or its affiliates.
+ */
+
+/**
+ * DOC: kTLS handshake overview
+ *
+ * When a kernel TLS consumer wants to establish a TLS session, it
+ * makes an AF_TLSH Listener ready. When user space accepts on that
+ * listener, the kernel fabricates a user space socket endpoint on
+ * which a user space TLS library can perform the TLS handshake.
+ *
+ * Closing the user space descriptor signals to the kernel that the
+ * library handshake process is complete. If the library has managed
+ * to initialize the socket's TLS crypto_info, the kernel marks the
+ * handshake as a success.
+ */
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <net/sock.h>
+#include <net/tls.h>
+#include <net/handshake.h>
+
+#include <uapi/linux/handshake.h>
+
+static void tlsh_handshake_done(struct handshake_info *hsi,
+				struct sk_buff *skb, struct nlmsghdr *nlh,
+				struct nlattr *tb);
+
+struct tlsh_sock_info {
+	struct handshake_info	tsi_handshake_info;
+
+	void			(*tsi_handshake_done)(void *data, int status);
+	void			*tsi_handshake_data;
+
+	char			*tsi_tls_priorities;
+	key_serial_t		tsi_peerid;
+	key_serial_t		tsi_certificate;
+	key_serial_t		tsi_privkey;
+
+};
+
+static struct tlsh_sock_info *
+tlsh_sock_info_alloc(struct socket *sock, void (*done)(void *data, int status),
+		     void *data, const char *priorities)
+{
+	struct tlsh_sock_info *tsi;
+
+	tsi = kzalloc(sizeof(*tsi), GFP_KERNEL);
+	if (!tsi)
+		return NULL;
+
+	if (priorities != TLS_DEFAULT_PRIORITIES && strlen(priorities)) {
+		tsi->tsi_tls_priorities = kstrdup(priorities, GFP_KERNEL);
+		if (!tsi->tsi_tls_priorities) {
+			kfree(tsi);
+			return NULL;
+		}
+	}
+
+	sock_hold(sock->sk);
+	tsi->tsi_handshake_info.hi_done = tlsh_handshake_done,
+	tsi->tsi_handshake_info.hi_sock = sock;
+	tsi->tsi_handshake_info.hi_mcgrp = HANDSHAKE_NLGRP_TLS_13;
+	tsi->tsi_handshake_info.hi_protocol = HANDSHAKE_NL_PROTO_TLS_13;
+
+	tsi->tsi_handshake_done = done;
+	tsi->tsi_handshake_data = data;
+	tsi->tsi_peerid = HANDSHAKE_NO_PEERID;
+	tsi->tsi_certificate = HANDSHAKE_NO_CERT;
+	tsi->tsi_privkey = HANDSHAKE_NO_PRIVKEY;
+
+	return tsi;
+}
+
+static void tlsh_sock_info_free(struct tlsh_sock_info *tsi)
+{
+	if (!tsi)
+		return;
+
+	if (tsi->tsi_handshake_info.hi_sock)
+		__sock_put(tsi->tsi_handshake_info.hi_sock->sk);
+	kfree(tsi->tsi_tls_priorities);
+	kfree(tsi);
+}
+
+static const struct nla_policy
+handshake_nl_attr_tls_policy[HANDSHAKE_NL_ATTR_MAX + 1] = {
+	[HANDSHAKE_NL_ATTR_TLS_TYPE] = {
+		.type = NLA_U32
+	},
+	[HANDSHAKE_NL_ATTR_TLS_AUTH] = {
+		.type = NLA_U32
+	},
+	[HANDSHAKE_NL_ATTR_TLS_PRIORITIES] = {
+		.type = NLA_STRING
+	},
+	[HANDSHAKE_NL_ATTR_TLS_X509_CERT] = {
+		.type = NLA_U32
+	},
+	[HANDSHAKE_NL_ATTR_TLS_X509_PRIVKEY] = {
+		.type = NLA_U32
+	},
+	[HANDSHAKE_NL_ATTR_TLS_PSK] = {
+		.type = NLA_U32
+	},
+	[HANDSHAKE_NL_ATTR_TLS_SESS_STATUS] = {
+		.type = NLA_U32,
+	},
+	[HANDSHAKE_NL_ATTR_TLS_PEERID] = {
+		.type = NLA_U32,
+	},
+};
+
+/**
+ * tlsh_handshake_done - call the handshake "done" callback for @sk.
+ * @hsi: socket on which the handshake was performed
+ * @skb: buffer containing incoming DONE msg
+ * @nlh: pointer to netlink message header
+ * @args: nested attributes for the TLS subsystem
+ *
+ * Eventually this will return information about the established
+ * session: whether it is authenticated, and if so, who the remote
+ * is.
+ */
+static void tlsh_handshake_done(struct handshake_info *hsi,
+				struct sk_buff *skb, struct nlmsghdr *nlh,
+				struct nlattr *args)
+{
+	struct tlsh_sock_info *tsi = container_of(hsi, struct tlsh_sock_info,
+						  tsi_handshake_info);
+	void (*done)(void *data, int status) = tsi->tsi_handshake_done;
+	struct nlattr *tb[HANDSHAKE_NL_ATTR_MAX + 1];
+	int err, status;
+
+	status = -EIO;
+	err = nla_parse_nested(tb, HANDSHAKE_NL_ATTR_MAX, args,
+			       handshake_nl_attr_tls_policy, NULL);
+	if (err < 0)
+		goto out;
+
+	if (!tb[HANDSHAKE_NL_ATTR_TLS_SESS_STATUS])
+		goto out;
+
+	switch (nla_get_u32(tb[HANDSHAKE_NL_ATTR_TLS_SESS_STATUS])) {
+	case HANDSHAKE_NL_TLS_SESS_STATUS_OK:
+		status = 0;
+		break;
+	case HANDSHAKE_NL_TLS_SESS_STATUS_REJECTED:
+		status = -EACCES;
+		break;
+	default:
+		status = -EIO;
+	}
+
+out:
+	done(tsi->tsi_handshake_data, status);
+	tlsh_sock_info_free(tsi);
+}
+
+/*
+ * Specifically for kernel TLS consumers: enable only TLS v1.3 and the
+ * ciphers that are supported by kTLS.
+ *
+ * This list is generated by hand from the supported ciphers found
+ * in include/uapi/linux/tls.h.
+ */
+#define KTLS_PRIORITIES \
+	"SECURE256:+SECURE128:-COMP-ALL" \
+	":-VERS-ALL:+VERS-TLS1.3:%NO_TICKETS" \
+	":-CIPHER-ALL:+CHACHA20-POLY1305:+AES-256-GCM:+AES-128-GCM:+AES-128-CCM"
+
+static int tlsh_nl_put_tls_priorities(struct sk_buff *msg,
+				      struct tlsh_sock_info *tsi)
+{
+	const char *priorities;
+	int ret;
+
+	priorities = tsi->tsi_tls_priorities ?
+		tsi->tsi_tls_priorities : KTLS_PRIORITIES;
+	ret = nla_put(msg, HANDSHAKE_NL_ATTR_TLS_PRIORITIES,
+		      strlen(priorities), priorities);
+	return ret < 0 ? ret : 0;
+}
+
+/**
+ * tlsh_nl_ch_anon_accept - callback to construct a ClientHello netlink reply
+ * @hsi: kernel handshake parameters to return
+ * @skb: sk_buff containing NL request
+ * @nlh: NL request's header
+ *
+ * If this function returns a valid pointer, caller must free it
+ * via nlmsg_free().
+ */
+static struct sk_buff *
+tlsh_nl_ch_anon_accept(struct handshake_info *hsi, struct sk_buff *skb,
+		       struct nlmsghdr *nlh)
+{
+	struct tlsh_sock_info *tsi = container_of(hsi, struct tlsh_sock_info,
+						  tsi_handshake_info);
+	struct nlattr *entry_attr;
+	struct nlmsghdr *hdr;
+	struct sk_buff *msg;
+	int ret;
+
+	ret = -ENOMEM;
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!msg)
+		goto out;
+
+	ret = -EMSGSIZE;
+	hdr = nlmsg_put(msg, NETLINK_CB(skb).portid, nlh->nlmsg_seq,
+			nlh->nlmsg_type, 0, 0);
+	if (!hdr)
+		goto out_free;
+
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_SOCKFD, hsi->hi_fd);
+	if (ret < 0)
+		goto out_free;
+
+	entry_attr = nla_nest_start(msg, HANDSHAKE_NL_ATTR_ACCEPT_RESP);
+	if (!entry_attr)
+		goto out_cancel;
+
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_TYPE,
+			  HANDSHAKE_NL_TLS_TYPE_CLIENTHELLO);
+	if (ret < 0)
+		goto out_cancel;
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_AUTH,
+			  HANDSHAKE_NL_TLS_AUTH_UNAUTH);
+	if (ret < 0)
+		goto out_cancel;
+	ret = tlsh_nl_put_tls_priorities(msg, tsi);
+	if (ret < 0)
+		goto out_cancel;
+	nla_nest_end(msg, entry_attr);
+
+	nlmsg_end(msg, hdr);
+	return msg;
+
+out_cancel:
+	nla_nest_cancel(msg, entry_attr);
+out_free:
+	nlmsg_free(msg);
+out:
+	return ERR_PTR(ret);
+}
+
+/**
+ * tls_client_hello_anon - request an anonymous TLS handshake on a socket
+ * @sock: connected socket on which to perform the handshake
+ * @done: function to call when the handshake has completed
+ * @data: token to pass back to @done
+ * @priorities: GnuTLS TLS priorities string, or NULL
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-ENOENT: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_anon(struct socket *sock,
+			  void (*done)(void *data, int status), void *data,
+			  const char *priorities)
+{
+	struct tlsh_sock_info *tsi;
+	int rc;
+
+	tsi = tlsh_sock_info_alloc(sock, done, data, priorities);
+	if (!tsi)
+		return -ENOMEM;
+	tsi->tsi_handshake_info.hi_accept = tlsh_nl_ch_anon_accept;
+
+	rc = handshake_request(&tsi->tsi_handshake_info, GFP_NOWAIT);
+	if (rc)
+		tlsh_sock_info_free(tsi);
+	return rc;
+}
+EXPORT_SYMBOL(tls_client_hello_anon);
+
+/**
+ * tlsh_nl_ch_x509_accept - callback to construct a ClientHello netlink reply
+ * @hsi: kernel handshake parameters to return
+ * @skb: sk_buff containing NL request
+ * @nlh: NL request's header
+ *
+ * If this function returns a valid pointer, caller must free it
+ * via nlmsg_free().
+ */
+static struct sk_buff *
+tlsh_nl_ch_x509_accept(struct handshake_info *hsi, struct sk_buff *skb,
+		       struct nlmsghdr *nlh)
+{
+	struct tlsh_sock_info *tsi = container_of(hsi, struct tlsh_sock_info,
+						  tsi_handshake_info);
+	struct nlattr *entry_attr;
+	struct nlmsghdr *hdr;
+	struct sk_buff *msg;
+	int ret;
+
+	ret = -ENOMEM;
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!msg)
+		goto out;
+
+	ret = -EMSGSIZE;
+	hdr = nlmsg_put(msg, NETLINK_CB(skb).portid, nlh->nlmsg_seq,
+			nlh->nlmsg_type, 0, 0);
+	if (!hdr)
+		goto out_free;
+
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_SOCKFD, hsi->hi_fd);
+	if (ret < 0)
+		goto out_free;
+
+	entry_attr = nla_nest_start(msg, HANDSHAKE_NL_ATTR_ACCEPT_RESP);
+	if (!entry_attr)
+		goto out_cancel;
+
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_TYPE,
+			  HANDSHAKE_NL_TLS_TYPE_CLIENTHELLO);
+	if (ret < 0)
+		goto out_cancel;
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_AUTH,
+			  HANDSHAKE_NL_TLS_AUTH_X509);
+	if (ret < 0)
+		goto out_cancel;
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_X509_CERT,
+			  tsi->tsi_certificate);
+	if (ret < 0)
+		goto out_cancel;
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_X509_PRIVKEY,
+			  tsi->tsi_privkey);
+	if (ret < 0)
+		goto out_cancel;
+	ret = tlsh_nl_put_tls_priorities(msg, tsi);
+	if (ret < 0)
+		goto out_cancel;
+	nla_nest_end(msg, entry_attr);
+
+	nlmsg_end(msg, hdr);
+	return msg;
+
+out_cancel:
+	nla_nest_cancel(msg, entry_attr);
+out_free:
+	nlmsg_free(msg);
+out:
+	return ERR_PTR(ret);
+}
+
+/**
+ * tls_client_hello_x509 - request an x.509-based TLS handshake on a socket
+ * @sock: connected socket on which to perform the handshake
+ * @done: function to call when the handshake has completed
+ * @data: token to pass back to @done
+ * @priorities: GnuTLS TLS priorities string
+ * @cert: serial number of key containing client's x.509 certificate
+ * @privkey: serial number of key containing client's private key
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-ENOENT: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_x509(struct socket *sock,
+			  void (*done)(void *data, int status), void *data,
+			  const char *priorities, key_serial_t cert,
+			  key_serial_t privkey)
+{
+	struct tlsh_sock_info *tsi;
+	int rc;
+
+	tsi = tlsh_sock_info_alloc(sock, done, data, priorities);
+	if (!tsi)
+		return -ENOMEM;
+	tsi->tsi_handshake_info.hi_accept = tlsh_nl_ch_x509_accept;
+	tsi->tsi_certificate = cert;
+	tsi->tsi_privkey = privkey;
+
+	rc = handshake_request(&tsi->tsi_handshake_info, GFP_NOWAIT);
+	if (rc)
+		tlsh_sock_info_free(tsi);
+	return rc;
+}
+EXPORT_SYMBOL(tls_client_hello_x509);
+
+/**
+ * tlsh_nl_ch_psk_accept - callback to construct a ClientHello netlink reply
+ * @hsi: kernel handshake parameters to return
+ * @skb: sk_buff containing NL request
+ * @nlh: NL request's header
+ *
+ * If this function returns a valid pointer, caller must free it
+ * via nlmsg_free().
+ */
+static struct sk_buff *
+tlsh_nl_ch_psk_accept(struct handshake_info *hsi, struct sk_buff *skb,
+		      struct nlmsghdr *nlh)
+{
+	struct tlsh_sock_info *tsi = container_of(hsi, struct tlsh_sock_info,
+						  tsi_handshake_info);
+	struct nlattr *entry_attr;
+	struct nlmsghdr *hdr;
+	struct sk_buff *msg;
+	int ret;
+
+	ret = -ENOMEM;
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!msg)
+		goto out;
+
+	ret = -EMSGSIZE;
+	hdr = nlmsg_put(msg, NETLINK_CB(skb).portid, nlh->nlmsg_seq,
+			nlh->nlmsg_type, 0, 0);
+	if (!hdr)
+		goto out_free;
+
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_SOCKFD, hsi->hi_fd);
+	if (ret < 0)
+		goto out_free;
+
+	entry_attr = nla_nest_start(msg, HANDSHAKE_NL_ATTR_ACCEPT_RESP);
+	if (!entry_attr)
+		goto out_cancel;
+
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_TYPE,
+			  HANDSHAKE_NL_TLS_TYPE_CLIENTHELLO);
+	if (ret < 0)
+		goto out_cancel;
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_AUTH,
+			  HANDSHAKE_NL_TLS_AUTH_PSK);
+	if (ret < 0)
+		goto out_cancel;
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_PSK,
+			  tsi->tsi_peerid);
+	if (ret < 0)
+		goto out_cancel;
+	ret = tlsh_nl_put_tls_priorities(msg, tsi);
+	if (ret < 0)
+		goto out_cancel;
+	nla_nest_end(msg, entry_attr);
+
+	nlmsg_end(msg, hdr);
+	return msg;
+
+out_cancel:
+	nla_nest_cancel(msg, entry_attr);
+out_free:
+	nlmsg_free(msg);
+out:
+	return ERR_PTR(ret);
+}
+
+/**
+ * tls_client_hello_psk - request a PSK-based TLS handshake on a socket
+ * @sock: connected socket on which to perform the handshake
+ * @done: function to call when the handshake has completed
+ * @data: token to pass back to @done
+ * @priorities: GnuTLS TLS priorities string
+ * @peerid: serial number of key containing TLS identity
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-ENOENT: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_client_hello_psk(struct socket *sock,
+			 void (*done)(void *data, int status), void *data,
+			 const char *priorities, key_serial_t peerid)
+{
+	struct tlsh_sock_info *tsi;
+	int rc;
+
+	tsi = tlsh_sock_info_alloc(sock, done, data, priorities);
+	if (!tsi)
+		return -ENOMEM;
+	tsi->tsi_handshake_info.hi_accept = tlsh_nl_ch_psk_accept;
+	tsi->tsi_peerid = peerid;
+
+	rc = handshake_request(&tsi->tsi_handshake_info, GFP_NOWAIT);
+	if (rc)
+		tlsh_sock_info_free(tsi);
+	return rc;
+}
+EXPORT_SYMBOL(tls_client_hello_psk);
+
+/**
+ * tlsh_nl_sh_accept - callback to construct a ServerHello netlink reply
+ * @hsi: kernel handshake parameters to return
+ * @skb: sk_buff containing NL request
+ * @nlh: NL request's header
+ *
+ * If this function returns a valid pointer, caller must free it
+ * via nlmsg_free().
+ */
+static struct sk_buff *
+tlsh_nl_sh_accept(struct handshake_info *hsi, struct sk_buff *skb,
+		  struct nlmsghdr *nlh)
+{
+	struct tlsh_sock_info *tsi = container_of(hsi, struct tlsh_sock_info,
+						  tsi_handshake_info);
+	struct nlattr *entry_attr;
+	struct nlmsghdr *hdr;
+	struct sk_buff *msg;
+	int ret;
+
+	ret = -ENOMEM;
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!msg)
+		goto out;
+
+	ret = -EMSGSIZE;
+	hdr = nlmsg_put(msg, NETLINK_CB(skb).portid, nlh->nlmsg_seq,
+			nlh->nlmsg_type, 0, 0);
+	if (!hdr)
+		goto out_free;
+
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_SOCKFD, hsi->hi_fd);
+	if (ret < 0)
+		goto out_free;
+
+	entry_attr = nla_nest_start(msg, HANDSHAKE_NL_ATTR_ACCEPT_RESP);
+	if (!entry_attr)
+		goto out_cancel;
+	ret = nla_put_u32(msg, HANDSHAKE_NL_ATTR_TLS_TYPE,
+			  HANDSHAKE_NL_TLS_TYPE_SERVERHELLO);
+	if (ret < 0)
+		goto out_cancel;
+	ret = tlsh_nl_put_tls_priorities(msg, tsi);
+	if (ret < 0)
+		goto out_cancel;
+	nla_nest_end(msg, entry_attr);
+
+	nlmsg_end(msg, hdr);
+	return msg;
+
+out_cancel:
+	nla_nest_cancel(msg, entry_attr);
+out_free:
+	nlmsg_free(msg);
+out:
+	return ERR_PTR(ret);
+}
+
+/**
+ * tls_server_hello - request a server TLS handshake on a socket
+ * @sock: connected socket on which to perform the handshake
+ * @done: function to call when the handshake has completed
+ * @data: token to pass back to @done
+ * @priorities: GnuTLS TLS priorities string
+ *
+ * Return values:
+ *   %0: Handshake request enqueue; ->done will be called when complete
+ *   %-ENOENT: No user agent is available
+ *   %-ENOMEM: Memory allocation failed
+ */
+int tls_server_hello(struct socket *sock, void (*done)(void *data, int status),
+		     void *data, const char *priorities)
+{
+	struct tlsh_sock_info *tsi;
+	int rc;
+
+	tsi = tlsh_sock_info_alloc(sock, done, data, priorities);
+	if (!tsi)
+		return -ENOMEM;
+	tsi->tsi_handshake_info.hi_accept = tlsh_nl_sh_accept;
+
+	rc = handshake_request(&tsi->tsi_handshake_info, GFP_KERNEL);
+	if (rc)
+		tlsh_sock_info_free(tsi);
+	return rc;
+}
+EXPORT_SYMBOL(tls_server_hello);
