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/smp_lock.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>
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 ;
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
} ;
static struct nfs_callback_data nfs_callback_info ;
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 ;
2006-08-23 04:06:07 +04:00
static const int nfs_set_port_min = 0 ;
static const int nfs_set_port_max = 65535 ;
2008-10-17 01:41:11 +04:00
/*
* If the kernel has IPv6 support available , always listen for
* both AF_INET and AF_INET6 requests .
*/
# if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
static const sa_family_t nfs_callback_family = AF_INET6 ;
# else
static const sa_family_t nfs_callback_family = AF_INET ;
# endif
2006-08-23 04:06:07 +04:00
static int param_set_port ( const char * val , struct kernel_param * kp )
{
char * endp ;
int num = simple_strtol ( val , & endp , 0 ) ;
if ( endp = = val | | * endp | | num < nfs_set_port_min | | num > nfs_set_port_max )
return - EINVAL ;
* ( ( int * ) kp - > arg ) = num ;
return 0 ;
}
module_param_call ( callback_tcpport , param_set_port , param_get_int ,
& nfs_callback_set_tcpport , 0644 ) ;
2005-04-17 02:20:36 +04:00
/*
* This is the callback kernel thread .
*/
2008-02-20 16:55:30 +03:00
static int
nfs_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
/*
* FIXME : do we really need to run this under the BKL ? If so , please
* add a comment about what it ' s intended to protect .
*/
lock_kernel ( ) ;
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 ) {
printk ( KERN_WARNING " %s: unexpected error "
" 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
}
unlock_kernel ( ) ;
2008-02-20 16:55:30 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
/*
2008-06-11 18:03:11 +04:00
* Bring up the callback thread if it is not already up .
2005-04-17 02:20:36 +04:00
*/
int nfs_callback_up ( void )
{
2008-02-11 18:00:20 +03:00
struct svc_serv * serv = NULL ;
2005-04-17 02:20:36 +04:00
int ret = 0 ;
2006-03-26 13:37:12 +04:00
mutex_lock ( & nfs_callback_mutex ) ;
2008-02-20 16:55:30 +03:00
if ( nfs_callback_info . users + + | | nfs_callback_info . task ! = NULL )
2005-04-17 02:20:36 +04:00
goto out ;
2009-03-19 03:46:29 +03:00
serv = svc_create ( & nfs4_callback_program , NFS4_CALLBACK_BUFSIZE , NULL ) ;
2005-04-17 02:20:36 +04:00
ret = - ENOMEM ;
if ( ! serv )
goto out_err ;
2007-02-12 11:53:29 +03:00
2009-03-19 03:46:21 +03:00
ret = svc_create_xprt ( serv , " tcp " , nfs_callback_family ,
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 " ,
nfs_callback_tcpport , nfs_callback_family ) ;
2007-02-12 11:53:29 +03:00
2008-06-11 18:03:11 +04:00
nfs_callback_info . rqst = svc_prepare_thread ( serv , & serv - > sv_pools [ 0 ] ) ;
if ( IS_ERR ( nfs_callback_info . rqst ) ) {
ret = PTR_ERR ( nfs_callback_info . rqst ) ;
nfs_callback_info . rqst = NULL ;
2008-02-11 18:00:20 +03:00
goto out_err ;
2008-02-20 16:55:30 +03:00
}
svc_sock_update_bufs ( serv ) ;
2008-06-11 18:03:11 +04:00
nfs_callback_info . task = kthread_run ( nfs_callback_svc ,
nfs_callback_info . rqst ,
2008-02-20 16:55:30 +03:00
" nfsv4-svc " ) ;
if ( IS_ERR ( nfs_callback_info . task ) ) {
ret = PTR_ERR ( nfs_callback_info . task ) ;
2008-06-11 18:03:11 +04:00
svc_exit_thread ( nfs_callback_info . rqst ) ;
nfs_callback_info . rqst = NULL ;
2008-02-20 16:55:30 +03:00
nfs_callback_info . task = NULL ;
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 ) ;
2005-04-17 02:20:36 +04:00
nfs_callback_info . users - - ;
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
*/
2006-08-23 04:06:08 +04:00
void nfs_callback_down ( void )
2005-04-17 02:20:36 +04:00
{
2006-03-26 13:37:12 +04:00
mutex_lock ( & nfs_callback_mutex ) ;
2006-03-20 21:44:49 +03:00
nfs_callback_info . users - - ;
2008-06-11 18:03:11 +04:00
if ( nfs_callback_info . users = = 0 & & nfs_callback_info . task ! = NULL ) {
2008-02-20 16:55:30 +03:00
kthread_stop ( nfs_callback_info . task ) ;
2008-06-11 18:03:11 +04:00
svc_exit_thread ( nfs_callback_info . rqst ) ;
nfs_callback_info . rqst = NULL ;
nfs_callback_info . task = NULL ;
}
2006-03-26 13:37:12 +04:00
mutex_unlock ( & nfs_callback_mutex ) ;
2005-04-17 02:20:36 +04:00
}
2008-12-24 00:18:34 +03:00
static 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 ) ;
/*
* 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 ;
/* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */
if ( memcmp ( p , " nfs@ " , 4 ) ! = 0 )
return SVC_DENIED ;
p + = 4 ;
if ( strcmp ( p , r - > cl_server ) ! = 0 )
return SVC_DENIED ;
return SVC_OK ;
}
2005-04-17 02:20:36 +04:00
static int nfs_callback_authenticate ( struct svc_rqst * rqstp )
{
2006-08-23 04:06:08 +04:00
struct nfs_client * clp ;
2008-02-21 10:57:45 +03:00
RPC_IFDEBUG ( char buf [ RPC_MAX_ADDRBUFLEN ] ) ;
2008-12-24 00:18:34 +03:00
int ret = SVC_OK ;
2005-04-17 02:20:36 +04:00
/* Don't talk to strangers */
2007-12-10 22:58:44 +03:00
clp = nfs_find_client ( svc_addr ( rqstp ) , 4 ) ;
2005-04-17 02:20:36 +04:00
if ( clp = = NULL )
return SVC_DROP ;
2007-02-12 11:53:32 +03:00
2008-05-03 00:42:44 +04:00
dprintk ( " %s: %s NFSv4 callback! \n " , __func__ ,
2007-02-12 11:53:32 +03:00
svc_print_addr ( rqstp , buf , sizeof ( buf ) ) ) ;
2005-04-17 02:20:36 +04:00
switch ( rqstp - > rq_authop - > flavour ) {
case RPC_AUTH_NULL :
if ( rqstp - > rq_proc ! = CB_NULL )
2008-12-24 00:18:34 +03:00
ret = SVC_DENIED ;
2005-04-17 02:20:36 +04:00
break ;
case RPC_AUTH_UNIX :
break ;
case RPC_AUTH_GSS :
2008-12-24 00:18:34 +03:00
ret = check_gss_callback_principal ( clp , rqstp ) ;
break ;
2005-04-17 02:20:36 +04:00
default :
2008-12-24 00:18:34 +03:00
ret = SVC_DENIED ;
2005-04-17 02:20:36 +04:00
}
2008-12-24 00:18:34 +03:00
nfs_put_client ( clp ) ;
return ret ;
2005-04-17 02:20:36 +04:00
}
/*
* Define NFS4 callback program
*/
static struct svc_version * nfs4_callback_version [ ] = {
[ 1 ] = & nfs4_callback_version1 ,
} ;
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 ,
} ;