355 lines · 8.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * linux/fs/nfs/callback.c * * Copyright (C) 2004 Trond Myklebust * * NFSv4 callback handling */ #include <linux/completion.h>#include <linux/ip.h>#include <linux/module.h>#include <linux/sched/signal.h>#include <linux/sunrpc/svc.h>#include <linux/sunrpc/svcsock.h>#include <linux/nfs_fs.h>#include <linux/errno.h>#include <linux/mutex.h>#include <linux/freezer.h>#include <linux/sunrpc/svcauth_gss.h>#include <linux/sunrpc/bc_xprt.h> #include <net/inet_sock.h> #include "nfs4_fs.h"#include "callback.h"#include "internal.h"#include "netns.h" #define NFSDBG_FACILITY NFSDBG_CALLBACK struct nfs_callback_data {	unsigned int users;	struct svc_serv *serv;}; static struct nfs_callback_data nfs_callback_info[NFS4_MAX_MINOR_VERSION + 1];static DEFINE_MUTEX(nfs_callback_mutex);static struct svc_program nfs4_callback_program; static int nfs4_callback_up_net(struct svc_serv *serv, struct net *net){	const struct cred *cred = current_cred();	int ret;	struct nfs_net *nn = net_generic(net, nfs_net_id); 	ret = svc_xprt_create(serv, "tcp", net, PF_INET,			      nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS,			      cred);	if (ret <= 0)		goto out_err;	nn->nfs_callback_tcpport = ret;	dprintk("NFS: Callback listener port = %u (af %u, net %x)\n",		nn->nfs_callback_tcpport, PF_INET, net->ns.inum); 	ret = svc_xprt_create(serv, "tcp", net, PF_INET6,			      nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS,			      cred);	if (ret > 0) {		nn->nfs_callback_tcpport6 = ret;		dprintk("NFS: Callback listener port = %u (af %u, net %x)\n",			nn->nfs_callback_tcpport6, PF_INET6, net->ns.inum);	} else if (ret != -EAFNOSUPPORT)		goto out_err;	return 0; out_err:	return (ret) ? ret : -ENOMEM;} /* * This is the NFSv4 callback kernel thread. */static intnfs4_callback_svc(void *vrqstp){	struct svc_rqst *rqstp = vrqstp; 	svc_thread_init_status(rqstp, 0); 	set_freezable(); 	while (!svc_thread_should_stop(rqstp))		svc_recv(rqstp, 0); 	svc_exit_thread(rqstp);	return 0;} static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt,		struct svc_serv *serv){	if (minorversion)		/*		 * Save the svc_serv in the transport so that it can		 * be referenced when the session backchannel is initialized		 */		xprt->bc_serv = serv;} static int nfs_callback_start_svc(int minorversion, struct rpc_xprt *xprt,				  struct svc_serv *serv){	int nrservs = nfs_callback_nr_threads;	int ret; 	nfs_callback_bc_serv(minorversion, xprt, serv); 	if (nrservs < NFS4_MIN_NR_CALLBACK_THREADS)		nrservs = NFS4_MIN_NR_CALLBACK_THREADS; 	if (serv->sv_nrthreads == nrservs)		return 0; 	ret = svc_set_num_threads(serv, 0, nrservs);	if (ret) {		svc_set_num_threads(serv, 0, 0);		return ret;	}	dprintk("nfs_callback_up: service started\n");	return 0;} static void nfs_callback_down_net(u32 minorversion, struct svc_serv *serv, struct net *net){	struct nfs_net *nn = net_generic(net, nfs_net_id); 	if (--nn->cb_users[minorversion])		return; 	dprintk("NFS: destroy per-net callback data; net=%x\n", net->ns.inum);	svc_xprt_destroy_all(serv, net, false);} static int nfs_callback_up_net(int minorversion, struct svc_serv *serv,			       struct net *net, struct rpc_xprt *xprt){	struct nfs_net *nn = net_generic(net, nfs_net_id);	int ret; 	if (nn->cb_users[minorversion]++)		return 0; 	dprintk("NFS: create per-net callback data; net=%x\n", net->ns.inum); 	ret = svc_bind(serv, net);	if (ret < 0) {		printk(KERN_WARNING "NFS: bind callback service failed\n");		goto err;	} 	ret = 0;	if (minorversion == 0)		ret = nfs4_callback_up_net(serv, net);	else if (xprt->ops->bc_setup)		set_bc_enabled(serv);	else		ret = -EPROTONOSUPPORT; 	if (ret < 0) {		printk(KERN_ERR "NFS: callback service start failed\n");		goto err;	}	return 0; err:	nn->cb_users[minorversion]--;	dprintk("NFS: Couldn't create callback socket: err = %d; "			"net = %x\n", ret, net->ns.inum);	return ret;} static struct svc_serv *nfs_callback_create_svc(int minorversion){	struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion];	int (*threadfn)(void *data);	struct svc_serv *serv; 	/*	 * Check whether we're already up and running.	 */	if (cb_info->serv)		return cb_info->serv; 	/*	 * Sanity check: if there's no task,	 * we should be the first user ...	 */	if (cb_info->users)		printk(KERN_WARNING "nfs_callback_create_svc: no kthread, %d users??\n",			cb_info->users); 	threadfn = nfs4_callback_svc;	serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE,			  threadfn);	if (!serv) {		printk(KERN_ERR "nfs_callback_create_svc: create service failed\n");		return ERR_PTR(-ENOMEM);	}	cb_info->serv = serv;	dprintk("nfs_callback_create_svc: service created\n");	return serv;} /* * Bring up the callback thread if it is not already up. */int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt){	struct svc_serv *serv;	struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion];	int ret;	struct net *net = xprt->xprt_net; 	mutex_lock(&nfs_callback_mutex); 	serv = nfs_callback_create_svc(minorversion);	if (IS_ERR(serv)) {		ret = PTR_ERR(serv);		goto err_create;	} 	ret = nfs_callback_up_net(minorversion, serv, net, xprt);	if (ret < 0)		goto err_net; 	ret = nfs_callback_start_svc(minorversion, xprt, serv);	if (ret < 0)		goto err_start; 	cb_info->users++;err_net:	if (!cb_info->users) {		xprt_svc_shutdown_bc(xprt);		svc_set_num_threads(cb_info->serv, 0, 0);		xprt_svc_destroy_nullify_bc(xprt, &cb_info->serv);	}err_create:	mutex_unlock(&nfs_callback_mutex);	return ret; err_start:	nfs_callback_down_net(minorversion, serv, net);	dprintk("NFS: Couldn't create server thread; err = %d\n", ret);	goto err_net;} /* * Kill the callback thread if it's no longer being used. */void nfs_callback_down(int minorversion, struct net *net, struct rpc_xprt *xprt){	struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion];	struct svc_serv *serv; 	mutex_lock(&nfs_callback_mutex);	serv = cb_info->serv;	xprt_svc_shutdown_bc(xprt);	nfs_callback_down_net(minorversion, serv, net);	cb_info->users--;	if (cb_info->users == 0) {		svc_set_num_threads(serv, 0, 0);		dprintk("nfs_callback_down: service destroyed\n");		xprt_svc_destroy_nullify_bc(xprt, &cb_info->serv);	}	mutex_unlock(&nfs_callback_mutex);} /* Boolean check of RPC_AUTH_GSS principal */intcheck_gss_callback_principal(struct nfs_client *clp, struct svc_rqst *rqstp){	char *p = rqstp->rq_cred.cr_principal; 	if (rqstp->rq_authop->flavour != RPC_AUTH_GSS)		return 1; 	/* No RPC_AUTH_GSS on NFSv4.1 back channel yet */	if (clp->cl_minorversion != 0)		return 0;	/*	 * It might just be a normal user principal, in which case	 * userspace won't bother to tell us the name at all.	 */	if (p == NULL)		return 0; 	/*	 * Did we get the acceptor from userland during the SETCLIENID	 * negotiation?	 */	if (clp->cl_acceptor)		return !strcmp(p, clp->cl_acceptor); 	/*	 * Otherwise try to verify it using the cl_hostname. Note that this	 * doesn't work if a non-canonical hostname was used in the devname.	 */ 	/* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */ 	if (memcmp(p, "nfs@", 4) != 0)		return 0;	p += 4;	if (strcmp(p, clp->cl_hostname) != 0)		return 0;	return 1;} /* * pg_authenticate method for nfsv4 callback threads. * * The authflavor has been negotiated, so an incorrect flavor is a server * bug. Deny packets with incorrect authflavor. * * All other checking done after NFS decoding where the nfs_client can be * found in nfs4_callback_compound */static enum svc_auth_status nfs_callback_authenticate(struct svc_rqst *rqstp){	rqstp->rq_auth_stat = rpc_autherr_badcred; 	switch (rqstp->rq_authop->flavour) {	case RPC_AUTH_NULL:		if (rqstp->rq_proc != CB_NULL)			return SVC_DENIED;		break;	case RPC_AUTH_GSS:		/* No RPC_AUTH_GSS support yet in NFSv4.1 */		 if (svc_is_backchannel(rqstp))			return SVC_DENIED;	} 	rqstp->rq_auth_stat = rpc_auth_ok;	return SVC_OK;} /* * Define NFS4 callback program */static const struct svc_version *nfs4_callback_version[] = {	[1] = &nfs4_callback_version1,	[4] = &nfs4_callback_version4,}; static struct svc_program nfs4_callback_program = {	.pg_prog = NFS4_CALLBACK,			/* RPC service number */	.pg_nvers = ARRAY_SIZE(nfs4_callback_version),	/* Number of entries */	.pg_vers = nfs4_callback_version,		/* version table */	.pg_name = "NFSv4 callback",			/* service name */	.pg_class = "nfs",				/* authentication class */	.pg_authenticate = nfs_callback_authenticate,	.pg_init_request = svc_generic_init_request,	.pg_rpcbind_set	= svc_generic_rpcbind_set,};