diff options
Diffstat (limited to 'fs/nfs/callback.c')
| -rw-r--r-- | fs/nfs/callback.c | 427 | 
1 files changed, 256 insertions, 171 deletions
diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index aeec017fe81..073b4cf67ed 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -9,23 +9,22 @@  #include <linux/completion.h>  #include <linux/ip.h>  #include <linux/module.h> -#include <linux/smp_lock.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/kthread.h>  #include <linux/sunrpc/svcauth_gss.h> -#if defined(CONFIG_NFS_V4_1)  #include <linux/sunrpc/bc_xprt.h> -#endif  #include <net/inet_sock.h>  #include "nfs4_fs.h"  #include "callback.h"  #include "internal.h" +#include "netns.h"  #define NFSDBG_FACILITY NFSDBG_CALLBACK @@ -40,31 +39,32 @@ 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; -unsigned int nfs_callback_set_tcpport; -unsigned short nfs_callback_tcpport; -unsigned short nfs_callback_tcpport6; -#define NFS_CALLBACK_MAXPORTNR (65535U) - -static int param_set_portnr(const char *val, const struct kernel_param *kp) +static int nfs4_callback_up_net(struct svc_serv *serv, struct net *net)  { -	unsigned long num;  	int ret; +	struct nfs_net *nn = net_generic(net, nfs_net_id); + +	ret = svc_create_xprt(serv, "tcp", net, PF_INET, +				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); +	if (ret <= 0) +		goto out_err; +	nn->nfs_callback_tcpport = ret; +	dprintk("NFS: Callback listener port = %u (af %u, net %p)\n", +			nn->nfs_callback_tcpport, PF_INET, net); -	if (!val) -		return -EINVAL; -	ret = strict_strtoul(val, 0, &num); -	if (ret == -EINVAL || num > NFS_CALLBACK_MAXPORTNR) -		return -EINVAL; -	*((unsigned int *)kp->arg) = num; +	ret = svc_create_xprt(serv, "tcp", net, PF_INET6, +				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); +	if (ret > 0) { +		nn->nfs_callback_tcpport6 = ret; +		dprintk("NFS: Callback listener port = %u (af %u, net %p)\n", +				nn->nfs_callback_tcpport6, PF_INET6, net); +	} else if (ret != -EAFNOSUPPORT) +		goto out_err;  	return 0; -} -static struct kernel_param_ops param_ops_portnr = { -	.set = param_set_portnr, -	.get = param_get_uint, -}; -#define param_check_portnr(name, p) __param_check(name, p, unsigned int); -module_param_named(callback_tcpport, nfs_callback_set_tcpport, portnr, 0644); +out_err: +	return (ret) ? ret : -ENOMEM; +}  /*   * This is the NFSv4 callback kernel thread. @@ -72,7 +72,7 @@ module_param_named(callback_tcpport, nfs_callback_set_tcpport, portnr, 0644);  static int  nfs4_callback_svc(void *vrqstp)  { -	int err, preverr = 0; +	int err;  	struct svc_rqst *rqstp = vrqstp;  	set_freezable(); @@ -82,20 +82,8 @@ nfs4_callback_svc(void *vrqstp)  		 * Listen for a request on the socket  		 */  		err = svc_recv(rqstp, MAX_SCHEDULE_TIMEOUT); -		if (err == -EAGAIN || err == -EINTR) { -			preverr = err; -			continue; -		} -		if (err < 0) { -			if (err != preverr) { -				printk(KERN_WARNING "%s: unexpected error " -					"from svc_recv (%d)\n", __func__, err); -				preverr = err; -			} -			schedule_timeout_uninterruptible(HZ); +		if (err == -EAGAIN || err == -EINTR)  			continue; -		} -		preverr = err;  		svc_process(rqstp);  	}  	return 0; @@ -104,39 +92,24 @@ nfs4_callback_svc(void *vrqstp)  /*   * Prepare to bring up the NFSv4 callback service   */ -struct svc_rqst * +static struct svc_rqst *  nfs4_callback_up(struct svc_serv *serv)  { -	int ret; - -	ret = svc_create_xprt(serv, "tcp", &init_net, PF_INET, -				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); -	if (ret <= 0) -		goto out_err; -	nfs_callback_tcpport = ret; -	dprintk("NFS: Callback listener port = %u (af %u)\n", -			nfs_callback_tcpport, PF_INET); - -	ret = svc_create_xprt(serv, "tcp", &init_net, PF_INET6, -				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); -	if (ret > 0) { -		nfs_callback_tcpport6 = ret; -		dprintk("NFS: Callback listener port = %u (af %u)\n", -				nfs_callback_tcpport6, PF_INET6); -	} else if (ret == -EAFNOSUPPORT) -		ret = 0; -	else -		goto out_err; - -	return svc_prepare_thread(serv, &serv->sv_pools[0]); - -out_err: -	if (ret == 0) -		ret = -ENOMEM; -	return ERR_PTR(ret); +	return svc_prepare_thread(serv, &serv->sv_pools[0], NUMA_NO_NODE);  }  #if defined(CONFIG_NFS_V4_1) +static int nfs41_callback_up_net(struct svc_serv *serv, struct net *net) +{ +	/* +	 * Create an svc_sock for the back channel service that shares the +	 * fore channel connection. +	 * Returns the input port (0) and sets the svc_serv bc_xprt on success +	 */ +	return svc_create_xprt(serv, "tcp-bc", net, PF_INET, 0, +			      SVC_SOCK_ANONYMOUS); +} +  /*   * The callback service for NFSv4.1 callbacks   */ @@ -152,6 +125,9 @@ nfs41_callback_svc(void *vrqstp)  	set_freezable();  	while (!kthread_should_stop()) { +		if (try_to_freeze()) +			continue; +  		prepare_to_wait(&serv->sv_cb_waitq, &wq, TASK_INTERRUPTIBLE);  		spin_lock_bh(&serv->sv_cb_lock);  		if (!list_empty(&serv->sv_cb_list)) { @@ -175,147 +151,257 @@ nfs41_callback_svc(void *vrqstp)  /*   * Bring up the NFSv4.1 callback service   */ -struct svc_rqst * -nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt) +static struct svc_rqst * +nfs41_callback_up(struct svc_serv *serv)  { -	struct svc_xprt *bc_xprt; -	struct svc_rqst *rqstp = ERR_PTR(-ENOMEM); - -	dprintk("--> %s\n", __func__); -	/* Create a svc_sock for the service */ -	bc_xprt = svc_sock_create(serv, xprt->prot); -	if (!bc_xprt) -		goto out; - -	/* -	 * Save the svc_serv in the transport so that it can -	 * be referenced when the session backchannel is initialized -	 */ -	serv->bc_xprt = bc_xprt; -	xprt->bc_serv = serv; +	struct svc_rqst *rqstp;  	INIT_LIST_HEAD(&serv->sv_cb_list);  	spin_lock_init(&serv->sv_cb_lock);  	init_waitqueue_head(&serv->sv_cb_waitq); -	rqstp = svc_prepare_thread(serv, &serv->sv_pools[0]); -	if (IS_ERR(rqstp)) -		svc_sock_destroy(bc_xprt); -out: -	dprintk("--> %s return %p\n", __func__, rqstp); +	rqstp = svc_prepare_thread(serv, &serv->sv_pools[0], NUMA_NO_NODE); +	if (IS_ERR(rqstp)) { +		svc_xprt_put(serv->sv_bc_xprt); +		serv->sv_bc_xprt = NULL; +	} +	dprintk("--> %s return %d\n", __func__, PTR_ERR_OR_ZERO(rqstp));  	return rqstp;  } -static inline int nfs_minorversion_callback_svc_setup(u32 minorversion, -		struct svc_serv *serv, struct rpc_xprt *xprt, +static void nfs_minorversion_callback_svc_setup(struct svc_serv *serv,  		struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp))  { -	if (minorversion) { -		*rqstpp = nfs41_callback_up(serv, xprt); -		*callback_svc = nfs41_callback_svc; -	} -	return minorversion; +	*rqstpp = nfs41_callback_up(serv); +	*callback_svc = nfs41_callback_svc;  }  static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, -		struct nfs_callback_data *cb_info) +		struct svc_serv *serv)  {  	if (minorversion) -		xprt->bc_serv = cb_info->serv; +		/* +		 * Save the svc_serv in the transport so that it can +		 * be referenced when the session backchannel is initialized +		 */ +		xprt->bc_serv = serv;  }  #else -static inline int nfs_minorversion_callback_svc_setup(u32 minorversion, -		struct svc_serv *serv, struct rpc_xprt *xprt, -		struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp)) +static int nfs41_callback_up_net(struct svc_serv *serv, struct net *net)  {  	return 0;  } +static void nfs_minorversion_callback_svc_setup(struct svc_serv *serv, +		struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp)) +{ +	*rqstpp = ERR_PTR(-ENOTSUPP); +	*callback_svc = ERR_PTR(-ENOTSUPP); +} +  static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, -		struct nfs_callback_data *cb_info) +		struct svc_serv *serv)  {  }  #endif /* CONFIG_NFS_V4_1 */ -/* - * Bring up the callback thread if it is not already up. - */ -int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) +static int nfs_callback_start_svc(int minorversion, struct rpc_xprt *xprt, +				  struct svc_serv *serv)  { -	struct svc_serv *serv = NULL;  	struct svc_rqst *rqstp;  	int (*callback_svc)(void *vrqstp);  	struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; -	char svc_name[12]; -	int ret = 0; -	int minorversion_setup; +	int ret; -	mutex_lock(&nfs_callback_mutex); -	if (cb_info->users++ || cb_info->task != NULL) { -		nfs_callback_bc_serv(minorversion, xprt, cb_info); -		goto out; -	} -	serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); -	if (!serv) { -		ret = -ENOMEM; -		goto out_err; -	} +	nfs_callback_bc_serv(minorversion, xprt, serv); + +	if (cb_info->task) +		return 0; -	minorversion_setup =  nfs_minorversion_callback_svc_setup(minorversion, -					serv, xprt, &rqstp, &callback_svc); -	if (!minorversion_setup) { +	switch (minorversion) { +	case 0:  		/* v4.0 callback setup */  		rqstp = nfs4_callback_up(serv);  		callback_svc = nfs4_callback_svc; +		break; +	default: +		nfs_minorversion_callback_svc_setup(serv, +				&rqstp, &callback_svc);  	} -	if (IS_ERR(rqstp)) { -		ret = PTR_ERR(rqstp); -		goto out_err; -	} +	if (IS_ERR(rqstp)) +		return PTR_ERR(rqstp);  	svc_sock_update_bufs(serv); -	sprintf(svc_name, "nfsv4.%u-svc", minorversion);  	cb_info->serv = serv;  	cb_info->rqst = rqstp; -	cb_info->task = kthread_run(callback_svc, cb_info->rqst, svc_name); +	cb_info->task = kthread_run(callback_svc, cb_info->rqst, +				    "nfsv4.%u-svc", minorversion);  	if (IS_ERR(cb_info->task)) {  		ret = PTR_ERR(cb_info->task);  		svc_exit_thread(cb_info->rqst);  		cb_info->rqst = NULL;  		cb_info->task = NULL; -		goto out_err; +		return ret;  	} -out: +	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=%p\n", net); +	svc_shutdown_net(serv, net); +} + +static int nfs_callback_up_net(int minorversion, struct svc_serv *serv, struct net *net) +{ +	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=%p\n", net); + +	ret = svc_bind(serv, net); +	if (ret < 0) { +		printk(KERN_WARNING "NFS: bind callback service failed\n"); +		goto err_bind; +	} + +	switch (minorversion) { +		case 0: +			ret = nfs4_callback_up_net(serv, net); +			break; +		case 1: +		case 2: +			ret = nfs41_callback_up_net(serv, net); +			break; +		default: +			printk(KERN_ERR "NFS: unknown callback version: %d\n", +					minorversion); +			ret = -EINVAL; +			break; +	} + +	if (ret < 0) { +		printk(KERN_ERR "NFS: callback service start failed\n"); +		goto err_socks; +	} +	return 0; + +err_socks: +	svc_rpcb_cleanup(serv, net); +err_bind: +	dprintk("NFS: Couldn't create callback socket: err = %d; " +			"net = %p\n", ret, net); +	return ret; +} + +static struct svc_serv *nfs_callback_create_svc(int minorversion) +{ +	struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; +	struct svc_serv *serv; + +	/* +	 * Check whether we're already up and running. +	 */ +	if (cb_info->task) { +		/* +		 * Note: increase service usage, because later in case of error +		 * svc_destroy() will be called. +		 */ +		svc_get(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); + +	serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); +	if (!serv) { +		printk(KERN_ERR "nfs_callback_create_svc: create service failed\n"); +		return ERR_PTR(-ENOMEM); +	} +	/* As there is only one thread we need to over-ride the +	 * default maximum of 80 connections +	 */ +	serv->sv_maxconn = 1024; +	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); +	if (ret < 0) +		goto err_net; + +	ret = nfs_callback_start_svc(minorversion, xprt, serv); +	if (ret < 0) +		goto err_start; + +	cb_info->users++;  	/*  	 * svc_create creates the svc_serv with sv_nrthreads == 1, and then  	 * svc_prepare_thread increments that. So we need to call svc_destroy  	 * on both success and failure so that the refcount is 1 when the  	 * thread exits.  	 */ -	if (serv) -		svc_destroy(serv); +err_net: +	svc_destroy(serv); +err_create:  	mutex_unlock(&nfs_callback_mutex);  	return ret; -out_err: -	dprintk("NFS: Couldn't create callback socket or server thread; " -		"err = %d\n", ret); -	cb_info->users--; -	goto out; + +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) +void nfs_callback_down(int minorversion, struct net *net)  {  	struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion];  	mutex_lock(&nfs_callback_mutex); +	nfs_callback_down_net(minorversion, cb_info->serv, net);  	cb_info->users--;  	if (cb_info->users == 0 && cb_info->task != NULL) {  		kthread_stop(cb_info->task); +		dprintk("nfs_callback_down: service stopped\n");  		svc_exit_thread(cb_info->rqst); +		dprintk("nfs_callback_down: service destroyed\n");  		cb_info->serv = NULL;  		cb_info->rqst = NULL;  		cb_info->task = NULL; @@ -323,58 +409,57 @@ void nfs_callback_down(int minorversion)  	mutex_unlock(&nfs_callback_mutex);  } -static int check_gss_callback_principal(struct nfs_client *clp, -					struct svc_rqst *rqstp) +/* Boolean check of RPC_AUTH_GSS principal */ +int +check_gss_callback_principal(struct nfs_client *clp, struct svc_rqst *rqstp)  { -	struct rpc_clnt *r = clp->cl_rpcclient; -	char *p = svc_gss_principal(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 SVC_DENIED; +		return 0;  	/* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */  	if (memcmp(p, "nfs@", 4) != 0) -		return SVC_DENIED; +		return 0;  	p += 4; -	if (strcmp(p, r->cl_server) != 0) -		return SVC_DENIED; -	return SVC_OK; +	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. Drop packets with incorrect authflavor. + * + * All other checking done after NFS decoding where the nfs_client can be + * found in nfs4_callback_compound + */  static int nfs_callback_authenticate(struct svc_rqst *rqstp)  { -	struct nfs_client *clp; -	RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]); -	int ret = SVC_OK; - -	/* Don't talk to strangers */ -	clp = nfs_find_client(svc_addr(rqstp), 4); -	if (clp == NULL) -		return SVC_DROP; - -	dprintk("%s: %s NFSv4 callback!\n", __func__, -			svc_print_addr(rqstp, buf, sizeof(buf))); -  	switch (rqstp->rq_authop->flavour) { -		case RPC_AUTH_NULL: -			if (rqstp->rq_proc != CB_NULL) -				ret = SVC_DENIED; -			break; -		case RPC_AUTH_UNIX: -			break; -		case RPC_AUTH_GSS: -			ret = check_gss_callback_principal(clp, rqstp); -			break; -		default: -			ret = SVC_DENIED; +	case RPC_AUTH_NULL: +		if (rqstp->rq_proc != CB_NULL) +			return SVC_DROP; +		break; +	case RPC_AUTH_GSS: +		/* No RPC_AUTH_GSS support yet in NFSv4.1 */ +		 if (svc_is_backchannel(rqstp)) +			return SVC_DROP;  	} -	nfs_put_client(clp); -	return ret; +	return SVC_OK;  }  /*  | 
