callback.c 9.5 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * 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>
I
Ingo Molnar 已提交
16
#include <linux/mutex.h>
17
#include <linux/freezer.h>
18
#include <linux/kthread.h>
19
#include <linux/sunrpc/svcauth_gss.h>
20 21 22
#if defined(CONFIG_NFS_V4_1)
#include <linux/sunrpc/bc_xprt.h>
#endif
23 24 25

#include <net/inet_sock.h>

26
#include "nfs4_fs.h"
L
Linus Torvalds 已提交
27
#include "callback.h"
28
#include "internal.h"
L
Linus Torvalds 已提交
29 30 31 32 33

#define NFSDBG_FACILITY NFSDBG_CALLBACK

struct nfs_callback_data {
	unsigned int users;
34
	struct svc_serv *serv;
35
	struct svc_rqst *rqst;
36
	struct task_struct *task;
L
Linus Torvalds 已提交
37 38 39
};

static struct nfs_callback_data nfs_callback_info;
I
Ingo Molnar 已提交
40
static DEFINE_MUTEX(nfs_callback_mutex);
L
Linus Torvalds 已提交
41 42
static struct svc_program nfs4_callback_program;

43
unsigned int nfs_callback_set_tcpport;
L
Linus Torvalds 已提交
44
unsigned short nfs_callback_tcpport;
45
unsigned short nfs_callback_tcpport6;
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
static const int nfs_set_port_min = 0;
static const int nfs_set_port_max = 65535;

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);
L
Linus Torvalds 已提交
61 62 63 64

/*
 * This is the callback kernel thread.
 */
65
static int
66
nfs4_callback_svc(void *vrqstp)
L
Linus Torvalds 已提交
67
{
68
	int err, preverr = 0;
69
	struct svc_rqst *rqstp = vrqstp;
L
Linus Torvalds 已提交
70

71
	set_freezable();
L
Linus Torvalds 已提交
72

73 74 75 76 77 78
	/*
	 * 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()) {
L
Linus Torvalds 已提交
79 80 81
		/*
		 * Listen for a request on the socket
		 */
82
		err = svc_recv(rqstp, MAX_SCHEDULE_TIMEOUT);
83 84
		if (err == -EAGAIN || err == -EINTR) {
			preverr = err;
L
Linus Torvalds 已提交
85
			continue;
86
		}
L
Linus Torvalds 已提交
87
		if (err < 0) {
88 89 90 91 92 93 94
			if (err != preverr) {
				printk(KERN_WARNING "%s: unexpected error "
					"from svc_recv (%d)\n", __func__, err);
				preverr = err;
			}
			schedule_timeout_uninterruptible(HZ);
			continue;
L
Linus Torvalds 已提交
95
		}
96
		preverr = err;
97
		svc_process(rqstp);
L
Linus Torvalds 已提交
98 99
	}
	unlock_kernel();
100
	return 0;
L
Linus Torvalds 已提交
101 102 103
}

/*
104
 * Prepare to bring up the NFSv4 callback service
L
Linus Torvalds 已提交
105
 */
106 107
struct svc_rqst *
nfs4_callback_up(struct svc_serv *serv)
L
Linus Torvalds 已提交
108
{
109
	int ret;
110

111
	ret = svc_create_xprt(serv, "tcp", PF_INET,
112
				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
113
	if (ret <= 0)
114
		goto out_err;
115
	nfs_callback_tcpport = ret;
116
	dprintk("NFS: Callback listener port = %u (af %u)\n",
117
			nfs_callback_tcpport, PF_INET);
118

119 120 121 122 123 124 125 126 127 128 129
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	ret = svc_create_xprt(serv, "tcp", 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)
		goto out_err;
#endif	/* defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) */

130 131 132 133 134 135 136 137
	return svc_prepare_thread(serv, &serv->sv_pools[0]);

out_err:
	if (ret == 0)
		ret = -ENOMEM;
	return ERR_PTR(ret);
}

138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
#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();

	/*
	 * 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()) {
		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);
	}
	unlock_kernel();
	nfs_callback_info.task = NULL;
	svc_exit_thread(rqstp);
	return 0;
}

/*
 * Bring up the NFSv4.1 callback service
 */
struct svc_rqst *
nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt)
{
	/*
	 * 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);
	return svc_prepare_thread(serv, &serv->sv_pools[0]);
}

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 */

231 232 233 234 235 236 237 238 239 240
/*
 * 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);
	char svc_name[12];
	int ret = 0;
241
	int minorversion_setup;
242 243

	mutex_lock(&nfs_callback_mutex);
244 245
	if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) {
		nfs_callback_bc_serv(minorversion, xprt, &nfs_callback_info);
246
		goto out;
247
	}
248 249 250 251 252 253
	serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL);
	if (!serv) {
		ret = -ENOMEM;
		goto out_err;
	}

254 255 256 257
	minorversion_setup =  nfs_minorversion_callback_svc_setup(minorversion,
					serv, xprt, &rqstp, &callback_svc);
	if (!minorversion_setup) {
		/* v4.0 callback setup */
258 259 260 261 262 263
		rqstp = nfs4_callback_up(serv);
		callback_svc = nfs4_callback_svc;
	}

	if (IS_ERR(rqstp)) {
		ret = PTR_ERR(rqstp);
264
		goto out_err;
265 266 267 268
	}

	svc_sock_update_bufs(serv);

269
	sprintf(svc_name, "nfsv4.%u-svc", minorversion);
270
	nfs_callback_info.serv = serv;
271 272
	nfs_callback_info.rqst = rqstp;
	nfs_callback_info.task = kthread_run(callback_svc,
273
					     nfs_callback_info.rqst,
274
					     svc_name);
275 276
	if (IS_ERR(nfs_callback_info.task)) {
		ret = PTR_ERR(nfs_callback_info.task);
277
		svc_exit_thread(nfs_callback_info.rqst);
278
		nfs_callback_info.serv = NULL;
279
		nfs_callback_info.rqst = NULL;
280 281 282
		nfs_callback_info.task = NULL;
		goto out_err;
	}
L
Linus Torvalds 已提交
283
out:
284 285
	/*
	 * svc_create creates the svc_serv with sv_nrthreads == 1, and then
286
	 * svc_prepare_thread increments that. So we need to call svc_destroy
287 288 289 290 291
	 * on both success and failure so that the refcount is 1 when the
	 * thread exits.
	 */
	if (serv)
		svc_destroy(serv);
I
Ingo Molnar 已提交
292
	mutex_unlock(&nfs_callback_mutex);
L
Linus Torvalds 已提交
293
	return ret;
294
out_err:
295 296
	dprintk("NFS: Couldn't create callback socket or server thread; "
		"err = %d\n", ret);
L
Linus Torvalds 已提交
297 298 299 300 301
	nfs_callback_info.users--;
	goto out;
}

/*
302
 * Kill the callback thread if it's no longer being used.
L
Linus Torvalds 已提交
303
 */
304
void nfs_callback_down(void)
L
Linus Torvalds 已提交
305
{
I
Ingo Molnar 已提交
306
	mutex_lock(&nfs_callback_mutex);
307
	nfs_callback_info.users--;
308
	if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL) {
309
		kthread_stop(nfs_callback_info.task);
310
		svc_exit_thread(nfs_callback_info.rqst);
311
		nfs_callback_info.serv = NULL;
312 313 314
		nfs_callback_info.rqst = NULL;
		nfs_callback_info.task = NULL;
	}
I
Ingo Molnar 已提交
315
	mutex_unlock(&nfs_callback_mutex);
L
Linus Torvalds 已提交
316 317
}

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
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;
}

L
Linus Torvalds 已提交
341 342
static int nfs_callback_authenticate(struct svc_rqst *rqstp)
{
343
	struct nfs_client *clp;
344
	RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]);
345
	int ret = SVC_OK;
L
Linus Torvalds 已提交
346 347

	/* Don't talk to strangers */
348
	clp = nfs_find_client(svc_addr(rqstp), 4);
L
Linus Torvalds 已提交
349 350
	if (clp == NULL)
		return SVC_DROP;
351

352
	dprintk("%s: %s NFSv4 callback!\n", __func__,
353 354
			svc_print_addr(rqstp, buf, sizeof(buf)));

L
Linus Torvalds 已提交
355 356 357
	switch (rqstp->rq_authop->flavour) {
		case RPC_AUTH_NULL:
			if (rqstp->rq_proc != CB_NULL)
358
				ret = SVC_DENIED;
L
Linus Torvalds 已提交
359 360 361 362
			break;
		case RPC_AUTH_UNIX:
			break;
		case RPC_AUTH_GSS:
363 364
			ret = check_gss_callback_principal(clp, rqstp);
			break;
L
Linus Torvalds 已提交
365
		default:
366
			ret = SVC_DENIED;
L
Linus Torvalds 已提交
367
	}
368 369
	nfs_put_client(clp);
	return ret;
L
Linus Torvalds 已提交
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
}

/*
 * 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,
};