2005-04-17 02:20:36 +04:00
/*
* 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/sunrpc/svc.h>
# include <linux/sunrpc/svcsock.h>
# include <linux/nfs_fs.h>
2012-10-02 03:33:18 +04:00
# include <linux/errno.h>
2006-03-26 13:37:12 +04:00
# include <linux/mutex.h>
2007-07-17 15:03:35 +04:00
# include <linux/freezer.h>
2008-02-20 16:55:30 +03:00
# include <linux/kthread.h>
2008-12-24 00:18:34 +03:00
# include <linux/sunrpc/svcauth_gss.h>
2009-04-01 17:23:08 +04:00
# include <linux/sunrpc/bc_xprt.h>
2005-12-27 07:43:12 +03:00
# include <net/inet_sock.h>
2005-06-22 21:16:21 +04:00
# include "nfs4_fs.h"
2005-04-17 02:20:36 +04:00
# include "callback.h"
2006-08-23 04:06:10 +04:00
# include "internal.h"
2012-08-20 18:00:36 +04:00
# include "netns.h"
2005-04-17 02:20:36 +04:00
# define NFSDBG_FACILITY NFSDBG_CALLBACK
struct nfs_callback_data {
unsigned int users ;
2009-04-01 17:23:08 +04:00
struct svc_serv * serv ;
2008-06-11 18:03:11 +04:00
struct svc_rqst * rqst ;
2008-02-20 16:55:30 +03:00
struct task_struct * task ;
2005-04-17 02:20:36 +04:00
} ;
2009-04-01 17:23:14 +04:00
static struct nfs_callback_data nfs_callback_info [ NFS4_MAX_MINOR_VERSION + 1 ] ;
2006-03-26 13:37:12 +04:00
static DEFINE_MUTEX ( nfs_callback_mutex ) ;
2005-04-17 02:20:36 +04:00
static struct svc_program nfs4_callback_program ;
2012-08-20 18:00:16 +04:00
static int nfs4_callback_up_net ( struct svc_serv * serv , struct net * net )
{
int ret ;
2012-08-20 18:00:36 +04:00
struct nfs_net * nn = net_generic ( net , nfs_net_id ) ;
2012-08-20 18:00:16 +04:00
ret = svc_create_xprt ( serv , " tcp " , net , PF_INET ,
nfs_callback_set_tcpport , SVC_SOCK_ANONYMOUS ) ;
if ( ret < = 0 )
goto out_err ;
2012-08-20 18:00:36 +04:00
nn - > nfs_callback_tcpport = ret ;
2012-08-20 18:00:16 +04:00
dprintk ( " NFS: Callback listener port = %u (af %u, net %p) \n " ,
2012-08-20 18:00:36 +04:00
nn - > nfs_callback_tcpport , PF_INET , net ) ;
2012-08-20 18:00:16 +04:00
ret = svc_create_xprt ( serv , " tcp " , net , PF_INET6 ,
nfs_callback_set_tcpport , SVC_SOCK_ANONYMOUS ) ;
if ( ret > 0 ) {
2012-08-20 18:00:41 +04:00
nn - > nfs_callback_tcpport6 = ret ;
2012-08-20 18:00:16 +04:00
dprintk ( " NFS: Callback listener port = %u (af %u, net %p) \n " ,
2012-08-20 18:00:41 +04:00
nn - > nfs_callback_tcpport6 , PF_INET6 , net ) ;
2012-08-20 18:00:16 +04:00
} else if ( ret ! = - EAFNOSUPPORT )
goto out_err ;
return 0 ;
out_err :
return ( ret ) ? ret : - ENOMEM ;
}
2005-04-17 02:20:36 +04:00
/*
2009-04-01 17:23:14 +04:00
* This is the NFSv4 callback kernel thread .
2005-04-17 02:20:36 +04:00
*/
2008-02-20 16:55:30 +03:00
static int
2009-04-01 17:22:56 +04:00
nfs4_callback_svc ( void * vrqstp )
2005-04-17 02:20:36 +04:00
{
2012-08-18 05:47:53 +04:00
int err ;
2008-02-20 16:55:30 +03:00
struct svc_rqst * rqstp = vrqstp ;
2005-04-17 02:20:36 +04:00
2007-07-17 15:03:35 +04:00
set_freezable ( ) ;
2005-04-17 02:20:36 +04:00
2008-02-20 16:55:30 +03:00
while ( ! kthread_should_stop ( ) ) {
2005-04-17 02:20:36 +04:00
/*
* Listen for a request on the socket
*/
2006-10-02 13:17:50 +04:00
err = svc_recv ( rqstp , MAX_SCHEDULE_TIMEOUT ) ;
2012-08-18 05:47:53 +04:00
if ( err = = - EAGAIN | | err = = - EINTR )
2005-04-17 02:20:36 +04:00
continue ;
2006-10-02 13:17:50 +04:00
svc_process ( rqstp ) ;
2005-04-17 02:20:36 +04:00
}
2008-02-20 16:55:30 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
/*
2009-04-01 17:22:56 +04:00
* Prepare to bring up the NFSv4 callback service
2005-04-17 02:20:36 +04:00
*/
2012-03-11 21:11:00 +04:00
static struct svc_rqst *
2012-08-20 18:00:21 +04:00
nfs4_callback_up ( struct svc_serv * serv )
2005-04-17 02:20:36 +04:00
{
2011-07-28 22:04:09 +04:00
return svc_prepare_thread ( serv , & serv - > sv_pools [ 0 ] , NUMA_NO_NODE ) ;
2009-04-01 17:22:56 +04:00
}
2009-04-01 17:23:08 +04:00
# if defined(CONFIG_NFS_V4_1)
2012-08-20 18:00:16 +04:00
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 ) ;
}
2009-04-01 17:23:08 +04:00
/*
* The callback service for NFSv4 .1 callbacks
*/
static int
nfs41_callback_svc ( void * vrqstp )
{
struct svc_rqst * rqstp = vrqstp ;
struct svc_serv * serv = rqstp - > rq_server ;
struct rpc_rqst * req ;
int error ;
DEFINE_WAIT ( wq ) ;
set_freezable ( ) ;
while ( ! kthread_should_stop ( ) ) {
2013-03-25 15:59:57 +04:00
if ( try_to_freeze ( ) )
continue ;
2009-04-01 17:23:08 +04:00
prepare_to_wait ( & serv - > sv_cb_waitq , & wq , TASK_INTERRUPTIBLE ) ;
spin_lock_bh ( & serv - > sv_cb_lock ) ;
if ( ! list_empty ( & serv - > sv_cb_list ) ) {
req = list_first_entry ( & serv - > sv_cb_list ,
struct rpc_rqst , rq_bc_list ) ;
list_del ( & req - > rq_bc_list ) ;
spin_unlock_bh ( & serv - > sv_cb_lock ) ;
dprintk ( " Invoking bc_svc_process() \n " ) ;
error = bc_svc_process ( serv , req , rqstp ) ;
dprintk ( " bc_svc_process() returned w/ error code= %d \n " ,
error ) ;
} else {
spin_unlock_bh ( & serv - > sv_cb_lock ) ;
schedule ( ) ;
}
finish_wait ( & serv - > sv_cb_waitq , & wq ) ;
}
return 0 ;
}
/*
* Bring up the NFSv4 .1 callback service
*/
2012-03-11 21:11:00 +04:00
static struct svc_rqst *
2012-08-20 18:00:21 +04:00
nfs41_callback_up ( struct svc_serv * serv )
2009-04-01 17:23:08 +04:00
{
2011-01-06 05:04:28 +03:00
struct svc_rqst * rqstp ;
2009-04-01 17:23:11 +04:00
2009-04-01 17:23:08 +04:00
INIT_LIST_HEAD ( & serv - > sv_cb_list ) ;
spin_lock_init ( & serv - > sv_cb_lock ) ;
init_waitqueue_head ( & serv - > sv_cb_waitq ) ;
2011-07-28 22:04:09 +04:00
rqstp = svc_prepare_thread ( serv , & serv - > sv_pools [ 0 ] , NUMA_NO_NODE ) ;
2011-01-06 05:04:28 +03:00
if ( IS_ERR ( rqstp ) ) {
2011-01-06 05:04:35 +03:00
svc_xprt_put ( serv - > sv_bc_xprt ) ;
serv - > sv_bc_xprt = NULL ;
2011-01-06 05:04:28 +03:00
}
2013-10-15 00:24:15 +04:00
dprintk ( " --> %s return %d \n " , __func__ , PTR_ERR_OR_ZERO ( rqstp ) ) ;
2009-04-01 17:23:11 +04:00
return rqstp ;
2009-04-01 17:23:08 +04:00
}
2012-10-02 03:33:18 +04:00
static void nfs_minorversion_callback_svc_setup ( struct svc_serv * serv ,
2009-04-01 17:23:08 +04:00
struct svc_rqst * * rqstpp , int ( * * callback_svc ) ( void * vrqstp ) )
{
2012-10-02 03:33:18 +04:00
* rqstpp = nfs41_callback_up ( serv ) ;
* callback_svc = nfs41_callback_svc ;
2009-04-01 17:23:08 +04:00
}
static inline void nfs_callback_bc_serv ( u32 minorversion , struct rpc_xprt * xprt ,
2012-08-20 18:00:21 +04:00
struct svc_serv * serv )
2009-04-01 17:23:08 +04:00
{
if ( minorversion )
2012-08-20 18:00:21 +04:00
/*
* Save the svc_serv in the transport so that it can
* be referenced when the session backchannel is initialized
*/
xprt - > bc_serv = serv ;
2009-04-01 17:23:08 +04:00
}
# else
2012-08-20 18:00:16 +04:00
static int nfs41_callback_up_net ( struct svc_serv * serv , struct net * net )
{
return 0 ;
}
2012-10-02 03:33:18 +04:00
static void nfs_minorversion_callback_svc_setup ( struct svc_serv * serv ,
2009-04-01 17:23:08 +04:00
struct svc_rqst * * rqstpp , int ( * * callback_svc ) ( void * vrqstp ) )
{
2012-10-02 03:33:18 +04:00
* rqstpp = ERR_PTR ( - ENOTSUPP ) ;
* callback_svc = ERR_PTR ( - ENOTSUPP ) ;
2009-04-01 17:23:08 +04:00
}
static inline void nfs_callback_bc_serv ( u32 minorversion , struct rpc_xprt * xprt ,
2012-08-20 18:00:21 +04:00
struct svc_serv * serv )
2009-04-01 17:23:08 +04:00
{
}
# endif /* CONFIG_NFS_V4_1 */
2012-08-20 18:00:26 +04:00
static int nfs_callback_start_svc ( int minorversion , struct rpc_xprt * xprt ,
struct svc_serv * serv )
{
struct svc_rqst * rqstp ;
int ( * callback_svc ) ( void * vrqstp ) ;
struct nfs_callback_data * cb_info = & nfs_callback_info [ minorversion ] ;
int ret ;
nfs_callback_bc_serv ( minorversion , xprt , serv ) ;
2012-08-20 18:00:31 +04:00
if ( cb_info - > task )
return 0 ;
2012-10-02 03:33:18 +04:00
switch ( minorversion ) {
case 0 :
2012-08-20 18:00:26 +04:00
/* v4.0 callback setup */
rqstp = nfs4_callback_up ( serv ) ;
callback_svc = nfs4_callback_svc ;
2012-10-02 03:33:18 +04:00
break ;
default :
nfs_minorversion_callback_svc_setup ( serv ,
& rqstp , & callback_svc ) ;
2012-08-20 18:00:26 +04:00
}
if ( IS_ERR ( rqstp ) )
return PTR_ERR ( rqstp ) ;
svc_sock_update_bufs ( serv ) ;
cb_info - > serv = serv ;
cb_info - > rqst = rqstp ;
2013-07-04 02:04:58 +04:00
cb_info - > task = kthread_run ( callback_svc , cb_info - > rqst ,
" nfsv4.%u-svc " , minorversion ) ;
2012-08-20 18:00:26 +04:00
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 ;
2012-10-16 20:30:44 +04:00
return ret ;
2012-08-20 18:00:26 +04:00
}
2014-08-03 21:03:08 +04:00
rqstp - > rq_task = cb_info - > task ;
2012-08-20 18:00:26 +04:00
dprintk ( " nfs_callback_up: service started \n " ) ;
return 0 ;
}
2012-08-20 18:00:46 +04:00
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 ) ;
}
2012-08-20 18:00:16 +04:00
static int nfs_callback_up_net ( int minorversion , struct svc_serv * serv , struct net * net )
{
2012-08-20 18:00:46 +04:00
struct nfs_net * nn = net_generic ( net , nfs_net_id ) ;
2012-08-20 18:00:16 +04:00
int ret ;
2012-08-20 18:00:46 +04:00
if ( nn - > cb_users [ minorversion ] + + )
return 0 ;
2012-08-20 18:00:16 +04:00
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 :
2013-05-22 20:50:38 +04:00
case 2 :
2012-08-20 18:00:16 +04:00
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 :
2012-08-20 18:00:31 +04:00
dprintk ( " NFS: Couldn't create callback socket: err = %d; "
" net = %p \n " , ret , net ) ;
2012-08-20 18:00:16 +04:00
return ret ;
}
2012-08-20 18:00:11 +04:00
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 ;
}
2009-04-01 17:22:56 +04:00
/*
* Bring up the callback thread if it is not already up .
*/
int nfs_callback_up ( u32 minorversion , struct rpc_xprt * xprt )
{
2012-08-20 18:00:11 +04:00
struct svc_serv * serv ;
2009-04-01 17:23:14 +04:00
struct nfs_callback_data * cb_info = & nfs_callback_info [ minorversion ] ;
2012-08-20 18:00:31 +04:00
int ret ;
2012-08-20 18:00:16 +04:00
struct net * net = xprt - > xprt_net ;
2009-04-01 17:22:56 +04:00
mutex_lock ( & nfs_callback_mutex ) ;
2012-08-20 18:00:11 +04:00
serv = nfs_callback_create_svc ( minorversion ) ;
if ( IS_ERR ( serv ) ) {
ret = PTR_ERR ( serv ) ;
goto err_create ;
}
2012-08-20 18:00:16 +04:00
ret = nfs_callback_up_net ( minorversion , serv , net ) ;
if ( ret < 0 )
goto err_net ;
2012-05-02 16:08:38 +04:00
2012-08-20 18:00:26 +04:00
ret = nfs_callback_start_svc ( minorversion , xprt , serv ) ;
if ( ret < 0 )
goto err_start ;
2008-02-20 16:55:30 +03:00
2012-08-20 18:00:31 +04:00
cb_info - > users + + ;
2008-02-11 18:00:20 +03:00
/*
* svc_create creates the svc_serv with sv_nrthreads = = 1 , and then
2008-02-20 16:55:30 +03:00
* svc_prepare_thread increments that . So we need to call svc_destroy
2008-02-11 18:00:20 +03:00
* on both success and failure so that the refcount is 1 when the
* thread exits .
*/
2012-08-20 18:00:31 +04:00
err_net :
2012-08-20 18:00:11 +04:00
svc_destroy ( serv ) ;
err_create :
2006-03-26 13:37:12 +04:00
mutex_unlock ( & nfs_callback_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
2012-08-20 18:00:26 +04:00
err_start :
2012-08-20 18:00:46 +04:00
nfs_callback_down_net ( minorversion , serv , net ) ;
2012-08-20 18:00:31 +04:00
dprintk ( " NFS: Couldn't create server thread; err = %d \n " , ret ) ;
goto err_net ;
2005-04-17 02:20:36 +04:00
}
/*
2008-06-11 18:03:11 +04:00
* Kill the callback thread if it ' s no longer being used .
2005-04-17 02:20:36 +04:00
*/
2012-08-20 18:00:06 +04:00
void nfs_callback_down ( int minorversion , struct net * net )
2005-04-17 02:20:36 +04:00
{
2009-04-01 17:23:14 +04:00
struct nfs_callback_data * cb_info = & nfs_callback_info [ minorversion ] ;
2006-03-26 13:37:12 +04:00
mutex_lock ( & nfs_callback_mutex ) ;
2012-08-20 18:00:46 +04:00
nfs_callback_down_net ( minorversion , cb_info - > serv , net ) ;
2009-04-01 17:23:14 +04:00
cb_info - > users - - ;
if ( cb_info - > users = = 0 & & cb_info - > task ! = NULL ) {
kthread_stop ( cb_info - > task ) ;
2012-08-20 18:00:51 +04:00
dprintk ( " nfs_callback_down: service stopped \n " ) ;
2009-04-01 17:23:14 +04:00
svc_exit_thread ( cb_info - > rqst ) ;
2012-08-20 18:00:51 +04:00
dprintk ( " nfs_callback_down: service destroyed \n " ) ;
2009-04-01 17:23:14 +04:00
cb_info - > serv = NULL ;
cb_info - > rqst = NULL ;
cb_info - > task = NULL ;
2008-06-11 18:03:11 +04:00
}
2006-03-26 13:37:12 +04:00
mutex_unlock ( & nfs_callback_mutex ) ;
2005-04-17 02:20:36 +04:00
}
2011-01-25 18:38:01 +03:00
/* Boolean check of RPC_AUTH_GSS principal */
int
check_gss_callback_principal ( struct nfs_client * clp , struct svc_rqst * rqstp )
2008-12-24 00:18:34 +03:00
{
2012-05-15 03:55:22 +04:00
char * p = rqstp - > rq_cred . cr_principal ;
2008-12-24 00:18:34 +03:00
2011-01-25 18:38:01 +03:00
if ( rqstp - > rq_authop - > flavour ! = RPC_AUTH_GSS )
return 1 ;
2011-01-06 05:04:33 +03:00
/* No RPC_AUTH_GSS on NFSv4.1 back channel yet */
if ( clp - > cl_minorversion ! = 0 )
2011-01-25 18:38:01 +03:00
return 0 ;
2008-12-24 00:18:34 +03:00
/*
* 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 )
2011-01-25 18:38:01 +03:00
return 0 ;
2008-12-24 00:18:34 +03:00
2014-06-22 04:52:17 +04:00
/*
* 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 .
*/
2008-12-24 00:18:34 +03:00
/* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */
if ( memcmp ( p , " nfs@ " , 4 ) ! = 0 )
2011-01-25 18:38:01 +03:00
return 0 ;
2008-12-24 00:18:34 +03:00
p + = 4 ;
2012-03-02 02:01:05 +04:00
if ( strcmp ( p , clp - > cl_hostname ) ! = 0 )
2011-01-25 18:38:01 +03:00
return 0 ;
return 1 ;
2008-12-24 00:18:34 +03:00
}
2011-01-25 18:38:01 +03:00
/*
* 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
*/
2005-04-17 02:20:36 +04:00
static int nfs_callback_authenticate ( struct svc_rqst * rqstp )
{
switch ( rqstp - > rq_authop - > flavour ) {
2011-01-25 18:38:01 +03:00
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 ;
2005-04-17 02:20:36 +04:00
}
2011-01-25 18:38:01 +03:00
return SVC_OK ;
2005-04-17 02:20:36 +04:00
}
/*
* Define NFS4 callback program
*/
static struct svc_version * nfs4_callback_version [ ] = {
[ 1 ] = & nfs4_callback_version1 ,
2009-12-05 21:19:01 +03:00
[ 4 ] = & nfs4_callback_version4 ,
2005-04-17 02:20:36 +04:00
} ;
static struct svc_stat nfs4_callback_stats ;
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_stats = & nfs4_callback_stats ,
. pg_authenticate = nfs_callback_authenticate ,
} ;