callback.c 9.8 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
#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)
{
188 189 190 191 192 193 194 195 196
	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;

197 198 199 200
	/*
	 * Save the svc_serv in the transport so that it can
	 * be referenced when the session backchannel is initialized
	 */
201
	serv->bc_xprt = bc_xprt;
202 203 204 205 206
	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);
207 208 209 210 211 212
	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);
	return rqstp;
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
}

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

246 247 248 249 250 251 252 253 254 255
/*
 * 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;
256
	int minorversion_setup;
257 258

	mutex_lock(&nfs_callback_mutex);
259 260
	if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) {
		nfs_callback_bc_serv(minorversion, xprt, &nfs_callback_info);
261
		goto out;
262
	}
263 264 265 266 267 268
	serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL);
	if (!serv) {
		ret = -ENOMEM;
		goto out_err;
	}

269 270 271 272
	minorversion_setup =  nfs_minorversion_callback_svc_setup(minorversion,
					serv, xprt, &rqstp, &callback_svc);
	if (!minorversion_setup) {
		/* v4.0 callback setup */
273 274 275 276 277 278
		rqstp = nfs4_callback_up(serv);
		callback_svc = nfs4_callback_svc;
	}

	if (IS_ERR(rqstp)) {
		ret = PTR_ERR(rqstp);
279
		goto out_err;
280 281 282 283
	}

	svc_sock_update_bufs(serv);

284
	sprintf(svc_name, "nfsv4.%u-svc", minorversion);
285
	nfs_callback_info.serv = serv;
286 287
	nfs_callback_info.rqst = rqstp;
	nfs_callback_info.task = kthread_run(callback_svc,
288
					     nfs_callback_info.rqst,
289
					     svc_name);
290 291
	if (IS_ERR(nfs_callback_info.task)) {
		ret = PTR_ERR(nfs_callback_info.task);
292
		svc_exit_thread(nfs_callback_info.rqst);
293
		nfs_callback_info.serv = NULL;
294
		nfs_callback_info.rqst = NULL;
295 296 297
		nfs_callback_info.task = NULL;
		goto out_err;
	}
L
Linus Torvalds 已提交
298
out:
299 300
	/*
	 * svc_create creates the svc_serv with sv_nrthreads == 1, and then
301
	 * svc_prepare_thread increments that. So we need to call svc_destroy
302 303 304 305 306
	 * on both success and failure so that the refcount is 1 when the
	 * thread exits.
	 */
	if (serv)
		svc_destroy(serv);
I
Ingo Molnar 已提交
307
	mutex_unlock(&nfs_callback_mutex);
L
Linus Torvalds 已提交
308
	return ret;
309
out_err:
310 311
	dprintk("NFS: Couldn't create callback socket or server thread; "
		"err = %d\n", ret);
L
Linus Torvalds 已提交
312 313 314 315 316
	nfs_callback_info.users--;
	goto out;
}

/*
317
 * Kill the callback thread if it's no longer being used.
L
Linus Torvalds 已提交
318
 */
319
void nfs_callback_down(void)
L
Linus Torvalds 已提交
320
{
I
Ingo Molnar 已提交
321
	mutex_lock(&nfs_callback_mutex);
322
	nfs_callback_info.users--;
323
	if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL) {
324
		kthread_stop(nfs_callback_info.task);
325
		svc_exit_thread(nfs_callback_info.rqst);
326
		nfs_callback_info.serv = NULL;
327 328 329
		nfs_callback_info.rqst = NULL;
		nfs_callback_info.task = NULL;
	}
I
Ingo Molnar 已提交
330
	mutex_unlock(&nfs_callback_mutex);
L
Linus Torvalds 已提交
331 332
}

333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
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 已提交
356 357
static int nfs_callback_authenticate(struct svc_rqst *rqstp)
{
358
	struct nfs_client *clp;
359
	RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]);
360
	int ret = SVC_OK;
L
Linus Torvalds 已提交
361 362

	/* Don't talk to strangers */
363
	clp = nfs_find_client(svc_addr(rqstp), 4);
L
Linus Torvalds 已提交
364 365
	if (clp == NULL)
		return SVC_DROP;
366

367
	dprintk("%s: %s NFSv4 callback!\n", __func__,
368 369
			svc_print_addr(rqstp, buf, sizeof(buf)));

L
Linus Torvalds 已提交
370 371 372
	switch (rqstp->rq_authop->flavour) {
		case RPC_AUTH_NULL:
			if (rqstp->rq_proc != CB_NULL)
373
				ret = SVC_DENIED;
L
Linus Torvalds 已提交
374 375 376 377
			break;
		case RPC_AUTH_UNIX:
			break;
		case RPC_AUTH_GSS:
378 379
			ret = check_gss_callback_principal(clp, rqstp);
			break;
L
Linus Torvalds 已提交
380
		default:
381
			ret = SVC_DENIED;
L
Linus Torvalds 已提交
382
	}
383 384
	nfs_put_client(clp);
	return ret;
L
Linus Torvalds 已提交
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
}

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