[PATCH RFC 10/15] SUNRPC: Expose TLS policy via the rpc_create() API
Chuck Lever
chuck.lever at oracle.com
Mon Apr 18 09:52:15 PDT 2022
Consumers use this API to specify RPC-over-TLS security policy for
each rpc_clnt. For the moment, this checks for TLS availability only
at the time the struct rpc_clnt is created.
Signed-off-by: Chuck Lever <chuck.lever at oracle.com>
---
include/linux/sunrpc/clnt.h | 1
include/linux/sunrpc/xprt.h | 1
include/linux/sunrpc/xprtsock.h | 1
include/trace/events/sunrpc.h | 41 +++++++++++++----
net/sunrpc/clnt.c | 81 +++++++++++++++++++++++++++++++---
net/sunrpc/xprtsock.c | 93 +++++++++++++++++++++++++++++++++++++++
6 files changed, 201 insertions(+), 17 deletions(-)
diff --git a/include/linux/sunrpc/clnt.h b/include/linux/sunrpc/clnt.h
index 14f169aec5c8..e10a19d136ca 100644
--- a/include/linux/sunrpc/clnt.h
+++ b/include/linux/sunrpc/clnt.h
@@ -146,6 +146,7 @@ struct rpc_create_args {
struct svc_xprt *bc_xprt; /* NFSv4.1 backchannel */
const struct cred *cred;
unsigned int max_connect;
+ enum rpc_xprtsec xprtsec_policy;
};
struct rpc_add_xprt_test {
diff --git a/include/linux/sunrpc/xprt.h b/include/linux/sunrpc/xprt.h
index 522bbf937957..8d654bc35dce 100644
--- a/include/linux/sunrpc/xprt.h
+++ b/include/linux/sunrpc/xprt.h
@@ -158,6 +158,7 @@ struct rpc_xprt_ops {
int (*enable_swap)(struct rpc_xprt *xprt);
void (*disable_swap)(struct rpc_xprt *xprt);
void (*inject_disconnect)(struct rpc_xprt *xprt);
+ int (*tls_handshake_sync)(struct rpc_xprt *xprt);
int (*bc_setup)(struct rpc_xprt *xprt,
unsigned int min_reqs);
size_t (*bc_maxpayload)(struct rpc_xprt *xprt);
diff --git a/include/linux/sunrpc/xprtsock.h b/include/linux/sunrpc/xprtsock.h
index 426c3bd516fe..d738a302b38b 100644
--- a/include/linux/sunrpc/xprtsock.h
+++ b/include/linux/sunrpc/xprtsock.h
@@ -58,6 +58,7 @@ struct sock_xprt {
struct work_struct error_worker;
struct work_struct recv_worker;
struct mutex recv_mutex;
+ struct completion handshake_done;
struct sockaddr_storage srcaddr;
unsigned short srcport;
int xprt_err;
diff --git a/include/trace/events/sunrpc.h b/include/trace/events/sunrpc.h
index 8ffc9c07bc69..a73b68e25a8c 100644
--- a/include/trace/events/sunrpc.h
+++ b/include/trace/events/sunrpc.h
@@ -149,36 +149,56 @@ TRACE_DEFINE_ENUM(RPC_XPRTSEC_MTLS);
{ RPC_XPRTSEC_TLS, "tls" }, \
{ RPC_XPRTSEC_MTLS, "mtls" })
+#define rpc_show_create_flags(flags) \
+ __print_flags(flags, "|", \
+ { RPC_CLNT_CREATE_HARDRTRY, "HARDRTRY" }, \
+ { RPC_CLNT_CREATE_AUTOBIND, "AUTOBIND" }, \
+ { RPC_CLNT_CREATE_NONPRIVPORT, "NONPRIVPORT" }, \
+ { RPC_CLNT_CREATE_NOPING, "NOPING" }, \
+ { RPC_CLNT_CREATE_DISCRTRY, "DISCRTRY" }, \
+ { RPC_CLNT_CREATE_QUIET, "QUIET" }, \
+ { RPC_CLNT_CREATE_INFINITE_SLOTS, "INFINITE_SLOTS" }, \
+ { RPC_CLNT_CREATE_NO_IDLE_TIMEOUT, "NO_IDLE_TIMEOUT" }, \
+ { RPC_CLNT_CREATE_NO_RETRANS_TIMEOUT, "NO_RETRANS_TIMEOUT" }, \
+ { RPC_CLNT_CREATE_SOFTERR, "SOFTERR" }, \
+ { RPC_CLNT_CREATE_REUSEPORT, "REUSEPORT" })
+
TRACE_EVENT(rpc_clnt_new,
TP_PROTO(
const struct rpc_clnt *clnt,
const struct rpc_xprt *xprt,
- const char *program,
- const char *server
+ const struct rpc_create_args *args
),
- TP_ARGS(clnt, xprt, program, server),
+ TP_ARGS(clnt, xprt, args),
TP_STRUCT__entry(
__field(unsigned int, client_id)
+ __field(unsigned long, xprtsec)
+ __field(unsigned long, flags)
+ __string(program, clnt->cl_program->name)
+ __string(server, xprt->servername)
__string(addr, xprt->address_strings[RPC_DISPLAY_ADDR])
__string(port, xprt->address_strings[RPC_DISPLAY_PORT])
- __string(program, program)
- __string(server, server)
),
TP_fast_assign(
__entry->client_id = clnt->cl_clid;
+ __entry->xprtsec = args->xprtsec_policy;
+ __entry->flags = args->flags;
+ __assign_str(program, clnt->cl_program->name);
+ __assign_str(server, xprt->servername);
__assign_str(addr, xprt->address_strings[RPC_DISPLAY_ADDR]);
__assign_str(port, xprt->address_strings[RPC_DISPLAY_PORT]);
- __assign_str(program, program);
- __assign_str(server, server);
),
- TP_printk("client=" SUNRPC_TRACE_CLID_SPECIFIER
- " peer=[%s]:%s program=%s server=%s",
+ TP_printk("client=" SUNRPC_TRACE_CLID_SPECIFIER " peer=[%s]:%s"
+ " program=%s server=%s xprtsec=%s flags=%s",
__entry->client_id, __get_str(addr), __get_str(port),
- __get_str(program), __get_str(server))
+ __get_str(program), __get_str(server),
+ rpc_show_xprtsec_policy(__entry->xprtsec),
+ rpc_show_create_flags(__entry->flags)
+ )
);
TRACE_EVENT(rpc_clnt_new_err,
@@ -1585,6 +1605,7 @@ DECLARE_EVENT_CLASS(rpc_tls_class,
), \
TP_ARGS(clnt, xprt))
+DEFINE_RPC_TLS_EVENT(unsupported);
DEFINE_RPC_TLS_EVENT(unavailable);
DEFINE_RPC_TLS_EVENT(not_started);
diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
index 856581018f10..1601a06deaf5 100644
--- a/net/sunrpc/clnt.c
+++ b/net/sunrpc/clnt.c
@@ -76,6 +76,7 @@ static int rpc_encode_header(struct rpc_task *task,
static int rpc_decode_header(struct rpc_task *task,
struct xdr_stream *xdr);
static int rpc_ping(struct rpc_clnt *clnt);
+static int rpc_starttls_sync(struct rpc_clnt *clnt);
static void rpc_check_timeout(struct rpc_task *task);
static void rpc_register_client(struct rpc_clnt *clnt)
@@ -433,7 +434,7 @@ static struct rpc_clnt * rpc_new_client(const struct rpc_create_args *args,
if (parent)
refcount_inc(&parent->cl_count);
- trace_rpc_clnt_new(clnt, xprt, program->name, args->servername);
+ trace_rpc_clnt_new(clnt, xprt, args);
return clnt;
out_no_path:
@@ -457,6 +458,7 @@ static struct rpc_clnt *rpc_create_xprt(struct rpc_create_args *args,
{
struct rpc_clnt *clnt = NULL;
struct rpc_xprt_switch *xps;
+ int err;
if (args->bc_xprt && args->bc_xprt->xpt_bc_xps) {
WARN_ON_ONCE(!(args->protocol & XPRT_TRANSPORT_BC));
@@ -477,13 +479,23 @@ static struct rpc_clnt *rpc_create_xprt(struct rpc_create_args *args,
if (IS_ERR(clnt))
return clnt;
- clnt->cl_xprtsec_policy = RPC_XPRTSEC_NONE;
- if (!(args->flags & RPC_CLNT_CREATE_NOPING)) {
- int err = rpc_ping(clnt);
- if (err != 0) {
- rpc_shutdown_client(clnt);
- return ERR_PTR(err);
+ clnt->cl_xprtsec_policy = args->xprtsec_policy;
+ switch (args->xprtsec_policy) {
+ case RPC_XPRTSEC_NONE:
+ if (!(args->flags & RPC_CLNT_CREATE_NOPING)) {
+ err = rpc_ping(clnt);
+ if (err != 0)
+ goto out_shutdown;
}
+ break;
+ case RPC_XPRTSEC_TLS:
+ err = rpc_starttls_sync(clnt);
+ if (err != 0)
+ goto out_shutdown;
+ break;
+ default:
+ err = -EINVAL;
+ goto out_shutdown;
}
clnt->cl_softrtry = 1;
@@ -503,6 +515,10 @@ static struct rpc_clnt *rpc_create_xprt(struct rpc_create_args *args,
clnt->cl_chatty = 1;
return clnt;
+
+out_shutdown:
+ rpc_shutdown_client(clnt);
+ return ERR_PTR(err);
}
/**
@@ -690,6 +706,7 @@ rpc_clone_client_set_auth(struct rpc_clnt *clnt, rpc_authflavor_t flavor)
.version = clnt->cl_vers,
.authflavor = flavor,
.cred = clnt->cl_cred,
+ .xprtsec_policy = clnt->cl_xprtsec_policy,
};
return __rpc_clone_client(&args, clnt);
}
@@ -2762,6 +2779,56 @@ static int rpc_ping(struct rpc_clnt *clnt)
return status;
}
+/**
+ * rpc_starttls_sync - Synchronously establish a TLS session
+ * @clnt: A fresh RPC client
+ *
+ * Return values:
+ * %0: TLS session established
+ * %-ENOPROTOOPT: underlying xprt does not support TLS
+ * %-EPERM: peer does not support TLS
+ * %-EACCES: TLS session could not be established
+ */
+static int rpc_starttls_sync(struct rpc_clnt *clnt)
+{
+ struct rpc_xprt *xprt = xprt_get(rcu_dereference(clnt->cl_xprt));
+ struct rpc_message msg = {
+ .rpc_proc = &rpcproc_null,
+ };
+ int err;
+
+ if (!xprt->ops->tls_handshake_sync) {
+ trace_rpc_tls_unsupported(clnt, xprt);
+ err = -ENOPROTOOPT;
+ goto out_put;
+ }
+
+ err = rpc_call_sync(clnt, &msg,
+ RPC_TASK_SOFT | RPC_TASK_SOFTCONN |
+ RPC_TASK_TLSCRED);
+ switch (err) {
+ case 0:
+ break;
+ case -EACCES:
+ case -EIO:
+ trace_rpc_tls_unavailable(clnt, xprt);
+ err = -EPERM;
+ fallthrough;
+ default:
+ goto out_put;
+ }
+
+ if (xprt->ops->tls_handshake_sync(xprt)) {
+ trace_rpc_tls_not_started(clnt, xprt);
+ err = -EACCES;
+ goto out_put;
+ }
+
+out_put:
+ xprt_put(xprt);
+ return err;
+}
+
struct rpc_cb_add_xprt_calldata {
struct rpc_xprt_switch *xps;
struct rpc_xprt *xprt;
diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
index e42ae84d7359..bbba9747f68d 100644
--- a/net/sunrpc/xprtsock.c
+++ b/net/sunrpc/xprtsock.c
@@ -48,6 +48,7 @@
#include <net/udp.h>
#include <net/tcp.h>
#include <net/tls.h>
+#include <net/tlsh.h>
#include <linux/bvec.h>
#include <linux/highmem.h>
@@ -197,6 +198,11 @@ static struct ctl_table sunrpc_table[] = {
*/
#define XS_IDLE_DISC_TO (5U * 60 * HZ)
+/*
+ * TLS handshake timeout.
+ */
+#define XS_TLS_HANDSHAKE_TO (20U * HZ)
+
#if IS_ENABLED(CONFIG_SUNRPC_DEBUG)
# undef RPC_DEBUG_DATA
# define RPCDBG_FACILITY RPCDBG_TRANS
@@ -2304,6 +2310,92 @@ static int xs_tcp_finish_connecting(struct rpc_xprt *xprt, struct socket *sock)
return kernel_connect(sock, xs_addr(xprt), xprt->addrlen, O_NONBLOCK);
}
+#if IS_ENABLED(CONFIG_TLS)
+
+static void xs_tcp_clear_discard(struct sock_xprt *transport)
+{
+ transport->recv.ignore_dr = false;
+ transport->recv.copied = 0;
+ transport->recv.offset = 0;
+ transport->recv.len = 0;
+}
+
+/**
+ * xs_tcp_tls_handshake_done - TLS handshake completion handler
+ * @data: address of xprt to wake
+ * @status: status of handshake
+ *
+ */
+static void xs_tcp_tls_handshake_done(void *data, int status)
+{
+ struct rpc_xprt *xprt = data;
+ struct sock_xprt *transport =
+ container_of(xprt, struct sock_xprt, xprt);
+
+ transport->xprt_err = status ? -EACCES : 0;
+ complete(&transport->handshake_done);
+ xprt_put(xprt);
+}
+
+/**
+ * xs_tcp_tls_handshake_sync - Perform a full TLS client handshake
+ * @xprt: transport on which to perform handshake
+ *
+ * Caller ensures there will be no other traffic on this transport.
+ *
+ * Return values:
+ * %0: Handshake completed successfully.
+ * Negative errno: handshake not started, or failed.
+ */
+static int xs_tcp_tls_handshake_sync(struct rpc_xprt *xprt)
+{
+ struct sock_xprt *transport =
+ container_of(xprt, struct sock_xprt, xprt);
+ int rc;
+
+ /* XXX: make it an XPRT_ flag instead? */
+ WRITE_ONCE(transport->recv.ignore_dr, true);
+
+ init_completion(&transport->handshake_done);
+
+ transport->xprt_err = -ETIMEDOUT;
+ rc = tls_client_hello_x509(transport->sock,
+ xs_tcp_tls_handshake_done, xprt_get(xprt),
+ TLSH_DEFAULT_PRIORITIES, TLSH_NO_PEERID,
+ TLSH_NO_CERT);
+ if (rc)
+ goto out;
+
+ rc = wait_for_completion_interruptible_timeout(&transport->handshake_done,
+ XS_TLS_HANDSHAKE_TO);
+ if (rc < 0)
+ goto out;
+
+ rc = transport->xprt_err;
+
+out:
+ xs_tcp_clear_discard(transport);
+ return rc;
+}
+
+#else /* CONFIG_TLS */
+
+/**
+ * xs_tcp_tls_handshake_sync - Perform a full TLS client handshake
+ * @xprt: transport on which to perform handshake
+ *
+ * Caller ensures there will be no other traffic on this transport.
+ *
+ * Return values:
+ * %-EACCES: handshake was not started.
+ */
+static int xs_tcp_tls_handshake_sync(struct rpc_xprt *xprt)
+{
+ return -EACCES;
+}
+
+#endif /*CONFIG_TLS */
+
/**
* xs_tcp_setup_socket - create a TCP socket and connect to a remote endpoint
* @work: queued work item
@@ -2752,6 +2844,7 @@ static const struct rpc_xprt_ops xs_tcp_ops = {
.enable_swap = xs_enable_swap,
.disable_swap = xs_disable_swap,
.inject_disconnect = xs_inject_disconnect,
+ .tls_handshake_sync = xs_tcp_tls_handshake_sync,
#ifdef CONFIG_SUNRPC_BACKCHANNEL
.bc_setup = xprt_setup_bc,
.bc_maxpayload = xs_tcp_bc_maxpayload,
More information about the Linux-nvme
mailing list