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>
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"
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 ;
2006-01-03 11:55:41 +03:00
unsigned int nfs_callback_set_tcpport ;
2005-04-17 02:20:36 +04:00
unsigned short nfs_callback_tcpport ;
2009-03-19 03:48:06 +03:00
unsigned short nfs_callback_tcpport6 ;
2009-08-09 23:06:19 +04:00
# define NFS_CALLBACK_MAXPORTNR (65535U)
2006-08-23 04:06:07 +04:00
2010-08-12 09:04:12 +04:00
static int param_set_portnr ( const char * val , const struct kernel_param * kp )
2006-08-23 04:06:07 +04:00
{
2009-08-09 23:06:19 +04:00
unsigned long num ;
int ret ;
if ( ! val )
return - EINVAL ;
ret = strict_strtoul ( val , 0 , & num ) ;
if ( ret = = - EINVAL | | num > NFS_CALLBACK_MAXPORTNR )
2006-08-23 04:06:07 +04:00
return - EINVAL ;
2009-08-09 23:06:19 +04:00
* ( ( unsigned int * ) kp - > arg ) = num ;
2006-08-23 04:06:07 +04:00
return 0 ;
}
2010-08-12 09:04:12 +04:00
static struct kernel_param_ops param_ops_portnr = {
. set = param_set_portnr ,
. get = param_get_uint ,
} ;
2009-08-09 23:06:19 +04:00
# define param_check_portnr(name, p) __param_check(name, p, unsigned int);
module_param_named ( callback_tcpport , nfs_callback_set_tcpport , portnr , 0644 ) ;
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
{
2008-04-08 23:40:07 +04:00
int err , preverr = 0 ;
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 ) ;
2008-04-08 23:40:07 +04:00
if ( err = = - EAGAIN | | err = = - EINTR ) {
preverr = err ;
2005-04-17 02:20:36 +04:00
continue ;
2008-04-08 23:40:07 +04:00
}
2005-04-17 02:20:36 +04:00
if ( err < 0 ) {
2008-04-08 23:40:07 +04:00
if ( err ! = preverr ) {
2012-01-26 22:32:23 +04:00
printk ( KERN_WARNING " NFS: %s: unexpected error "
2008-04-08 23:40:07 +04:00
" from svc_recv (%d) \n " , __func__ , err ) ;
preverr = err ;
}
schedule_timeout_uninterruptible ( HZ ) ;
continue ;
2005-04-17 02:20:36 +04:00
}
2008-04-08 23:40:07 +04:00
preverr = err ;
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
*/
2009-04-01 17:22:56 +04:00
struct svc_rqst *
2012-01-10 16:13:03 +04:00
nfs4_callback_up ( struct svc_serv * serv , struct rpc_xprt * xprt )
2005-04-17 02:20:36 +04:00
{
2009-04-01 17:22:56 +04:00
int ret ;
2007-02-12 11:53:29 +03:00
2012-01-10 16:13:03 +04:00
ret = svc_create_xprt ( serv , " tcp " , xprt - > xprt_net , PF_INET ,
2009-03-19 03:46:21 +03:00
nfs_callback_set_tcpport , SVC_SOCK_ANONYMOUS ) ;
2007-02-12 11:53:29 +03:00
if ( ret < = 0 )
2008-02-11 18:00:20 +03:00
goto out_err ;
2007-02-12 11:53:29 +03:00
nfs_callback_tcpport = ret ;
2008-10-17 01:41:11 +04:00
dprintk ( " NFS: Callback listener port = %u (af %u) \n " ,
2009-03-19 03:46:36 +03:00
nfs_callback_tcpport , PF_INET ) ;
2007-02-12 11:53:29 +03:00
2012-01-10 16:13:03 +04:00
ret = svc_create_xprt ( serv , " tcp " , xprt - > xprt_net , PF_INET6 ,
2009-03-19 03:48:06 +03:00
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 ) ;
2009-06-18 05:02:10 +04:00
} else if ( ret = = - EAFNOSUPPORT )
ret = 0 ;
else
2009-03-19 03:48:06 +03:00
goto out_err ;
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
out_err :
if ( ret = = 0 )
ret = - ENOMEM ;
return ERR_PTR ( ret ) ;
}
2009-04-01 17:23:08 +04:00
# if defined(CONFIG_NFS_V4_1)
/*
* 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 ( ) ) {
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
*/
struct svc_rqst *
nfs41_callback_up ( struct svc_serv * serv , struct rpc_xprt * xprt )
{
2011-01-06 05:04:28 +03:00
struct svc_rqst * rqstp ;
int ret ;
2009-04-01 17:23:11 +04:00
2011-01-06 05:04:28 +03:00
/*
* 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
*/
2012-01-10 16:13:03 +04:00
ret = svc_create_xprt ( serv , " tcp-bc " , xprt - > xprt_net , PF_INET , 0 ,
2011-01-06 05:04:28 +03:00
SVC_SOCK_ANONYMOUS ) ;
if ( ret < 0 ) {
rqstp = ERR_PTR ( ret ) ;
2009-04-01 17:23:11 +04:00
goto out ;
2011-01-06 05:04:28 +03:00
}
2009-04-01 17:23:11 +04:00
2009-04-01 17:23:08 +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 ;
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
}
2009-04-01 17:23:11 +04:00
out :
2011-01-06 05:04:28 +03:00
dprintk ( " --> %s return %ld \n " , __func__ ,
IS_ERR ( rqstp ) ? PTR_ERR ( rqstp ) : 0 ) ;
2009-04-01 17:23:11 +04:00
return rqstp ;
2009-04-01 17:23:08 +04:00
}
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 ) )
{
if ( minorversion ) {
* rqstpp = nfs41_callback_up ( serv , xprt ) ;
* callback_svc = nfs41_callback_svc ;
}
return minorversion ;
}
static inline void nfs_callback_bc_serv ( u32 minorversion , struct rpc_xprt * xprt ,
struct nfs_callback_data * cb_info )
{
if ( minorversion )
xprt - > bc_serv = cb_info - > 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 ) )
{
return 0 ;
}
static inline void nfs_callback_bc_serv ( u32 minorversion , struct rpc_xprt * xprt ,
struct nfs_callback_data * cb_info )
{
}
# endif /* CONFIG_NFS_V4_1 */
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 )
{
struct svc_serv * serv = NULL ;
struct svc_rqst * rqstp ;
int ( * callback_svc ) ( void * vrqstp ) ;
2009-04-01 17:23:14 +04:00
struct nfs_callback_data * cb_info = & nfs_callback_info [ minorversion ] ;
2009-04-01 17:22:56 +04:00
char svc_name [ 12 ] ;
int ret = 0 ;
2009-04-01 17:23:08 +04:00
int minorversion_setup ;
2009-04-01 17:22:56 +04:00
mutex_lock ( & nfs_callback_mutex ) ;
2009-04-01 17:23:14 +04:00
if ( cb_info - > users + + | | cb_info - > task ! = NULL ) {
nfs_callback_bc_serv ( minorversion , xprt , cb_info ) ;
2009-04-01 17:22:56 +04:00
goto out ;
2009-04-01 17:23:08 +04:00
}
2009-04-01 17:22:56 +04:00
serv = svc_create ( & nfs4_callback_program , NFS4_CALLBACK_BUFSIZE , NULL ) ;
if ( ! serv ) {
ret = - ENOMEM ;
goto out_err ;
}
2009-04-01 17:23:08 +04:00
minorversion_setup = nfs_minorversion_callback_svc_setup ( minorversion ,
serv , xprt , & rqstp , & callback_svc ) ;
if ( ! minorversion_setup ) {
/* v4.0 callback setup */
2012-01-10 16:13:03 +04:00
rqstp = nfs4_callback_up ( serv , xprt ) ;
2009-04-01 17:22:56 +04:00
callback_svc = nfs4_callback_svc ;
}
if ( IS_ERR ( rqstp ) ) {
ret = PTR_ERR ( rqstp ) ;
2008-02-11 18:00:20 +03:00
goto out_err ;
2008-02-20 16:55:30 +03:00
}
svc_sock_update_bufs ( serv ) ;
2009-04-01 17:22:56 +04:00
sprintf ( svc_name , " nfsv4.%u-svc " , minorversion ) ;
2009-04-01 17:23:14 +04:00
cb_info - > serv = serv ;
cb_info - > rqst = rqstp ;
cb_info - > task = kthread_run ( callback_svc , cb_info - > rqst , svc_name ) ;
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 ;
2008-02-20 16:55:30 +03:00
goto out_err ;
}
2005-04-17 02:20:36 +04:00
out :
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 .
*/
if ( serv )
svc_destroy ( serv ) ;
2006-03-26 13:37:12 +04:00
mutex_unlock ( & nfs_callback_mutex ) ;
2005-04-17 02:20:36 +04:00
return ret ;
2008-02-11 18:00:20 +03:00
out_err :
2008-10-17 01:41:11 +04:00
dprintk ( " NFS: Couldn't create callback socket or server thread; "
" err = %d \n " , ret ) ;
2009-04-01 17:23:14 +04:00
cb_info - > users - - ;
2005-04-17 02:20:36 +04:00
goto out ;
}
/*
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
*/
2009-04-01 17:23:14 +04:00
void nfs_callback_down ( int minorversion )
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 ) ;
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 ) ;
svc_exit_thread ( cb_info - > rqst ) ;
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
{
struct rpc_clnt * r = clp - > cl_rpcclient ;
char * p = svc_gss_principal ( rqstp ) ;
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
/* 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 ;
if ( strcmp ( p , r - > cl_server ) ! = 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 ,
} ;