aboutsummaryrefslogtreecommitdiffstats
path: root/fs/nfs/nfs4client.c
diff options
context:
space:
mode:
authorChuck Lever <chuck.lever@oracle.com>2012-09-14 17:24:32 -0400
committerTrond Myklebust <Trond.Myklebust@netapp.com>2012-10-01 18:33:33 -0400
commit05f4c350ee02e9461c6ae3a880ea326a06835e37 (patch)
tree847e24e55e47c06fcb46106b4922b915bb0109e6 /fs/nfs/nfs4client.c
parente984a55a7418f777407c7edbb2bdf5eb9559b5e2 (diff)
NFS: Discover NFSv4 server trunking when mounting
"Server trunking" is a fancy named for a multi-homed NFS server. Trunking might occur if a client sends NFS requests for a single workload to multiple network interfaces on the same server. There are some implications for NFSv4 state management that make it useful for a client to know if a single NFSv4 server instance is multi-homed. (Note this is only a consideration for NFSv4, not for legacy versions of NFS, which are stateless). If a client cares about server trunking, no NFSv4 operations can proceed until that client determines who it is talking to. Thus server IP trunking discovery must be done when the client first encounters an unfamiliar server IP address. The nfs_get_client() function walks the nfs_client_list and matches on server IP address. The outcome of that walk tells us immediately if we have an unfamiliar server IP address. It invokes nfs_init_client() in this case. Thus, nfs4_init_client() is a good spot to perform trunking discovery. Discovery requires a client to establish a fresh client ID, so our client will now send SETCLIENTID or EXCHANGE_ID as the first NFS operation after a successful ping, rather than waiting for an application to perform an operation that requires NFSv4 state. The exact process for detecting trunking is different for NFSv4.0 and NFSv4.1, so a minorversion-specific init_client callout method is introduced. CLID_INUSE recovery is important for the trunking discovery process. CLID_INUSE is a sign the server recognizes the client's nfs_client_id4 id string, but the client is using the wrong principal this time for the SETCLIENTID operation. The SETCLIENTID must be retried with a series of different principals until one works, and then the rest of trunking discovery can proceed. Signed-off-by: Chuck Lever <chuck.lever@oracle.com> Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Diffstat (limited to 'fs/nfs/nfs4client.c')
-rw-r--r--fs/nfs/nfs4client.c253
1 files changed, 253 insertions, 0 deletions
diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c
index 612f5ebaabac..14ddd4d30966 100644
--- a/fs/nfs/nfs4client.c
+++ b/fs/nfs/nfs4client.c
@@ -185,6 +185,7 @@ struct nfs_client *nfs4_init_client(struct nfs_client *clp,
185 rpc_authflavor_t authflavour) 185 rpc_authflavor_t authflavour)
186{ 186{
187 char buf[INET6_ADDRSTRLEN + 1]; 187 char buf[INET6_ADDRSTRLEN + 1];
188 struct nfs_client *old;
188 int error; 189 int error;
189 190
190 if (clp->cl_cons_state == NFS_CS_READY) { 191 if (clp->cl_cons_state == NFS_CS_READY) {
@@ -230,6 +231,17 @@ struct nfs_client *nfs4_init_client(struct nfs_client *clp,
230 231
231 if (!nfs4_has_session(clp)) 232 if (!nfs4_has_session(clp))
232 nfs_mark_client_ready(clp, NFS_CS_READY); 233 nfs_mark_client_ready(clp, NFS_CS_READY);
234
235 error = nfs4_discover_server_trunking(clp, &old);
236 if (error < 0)
237 goto error;
238 if (clp != old) {
239 clp->cl_preserve_clid = true;
240 nfs_put_client(clp);
241 clp = old;
242 atomic_inc(&clp->cl_count);
243 }
244
233 return clp; 245 return clp;
234 246
235error: 247error:
@@ -239,6 +251,247 @@ error:
239 return ERR_PTR(error); 251 return ERR_PTR(error);
240} 252}
241 253
254/*
255 * Returns true if the client IDs match
256 */
257static bool nfs4_match_clientids(struct nfs_client *a, struct nfs_client *b)
258{
259 if (a->cl_clientid != b->cl_clientid) {
260 dprintk("NFS: --> %s client ID %llx does not match %llx\n",
261 __func__, a->cl_clientid, b->cl_clientid);
262 return false;
263 }
264 dprintk("NFS: --> %s client ID %llx matches %llx\n",
265 __func__, a->cl_clientid, b->cl_clientid);
266 return true;
267}
268
269/*
270 * SETCLIENTID just did a callback update with the callback ident in
271 * "drop," but server trunking discovery claims "drop" and "keep" are
272 * actually the same server. Swap the callback IDs so that "keep"
273 * will continue to use the callback ident the server now knows about,
274 * and so that "keep"'s original callback ident is destroyed when
275 * "drop" is freed.
276 */
277static void nfs4_swap_callback_idents(struct nfs_client *keep,
278 struct nfs_client *drop)
279{
280 struct nfs_net *nn = net_generic(keep->cl_net, nfs_net_id);
281 unsigned int save = keep->cl_cb_ident;
282
283 if (keep->cl_cb_ident == drop->cl_cb_ident)
284 return;
285
286 dprintk("%s: keeping callback ident %u and dropping ident %u\n",
287 __func__, keep->cl_cb_ident, drop->cl_cb_ident);
288
289 spin_lock(&nn->nfs_client_lock);
290
291 idr_replace(&nn->cb_ident_idr, keep, drop->cl_cb_ident);
292 keep->cl_cb_ident = drop->cl_cb_ident;
293
294 idr_replace(&nn->cb_ident_idr, drop, save);
295 drop->cl_cb_ident = save;
296
297 spin_unlock(&nn->nfs_client_lock);
298}
299
300/**
301 * nfs40_walk_client_list - Find server that recognizes a client ID
302 *
303 * @new: nfs_client with client ID to test
304 * @result: OUT: found nfs_client, or new
305 * @cred: credential to use for trunking test
306 *
307 * Returns zero, a negative errno, or a negative NFS4ERR status.
308 * If zero is returned, an nfs_client pointer is planted in "result."
309 *
310 * NB: nfs40_walk_client_list() relies on the new nfs_client being
311 * the last nfs_client on the list.
312 */
313int nfs40_walk_client_list(struct nfs_client *new,
314 struct nfs_client **result,
315 struct rpc_cred *cred)
316{
317 struct nfs_net *nn = net_generic(new->cl_net, nfs_net_id);
318 struct nfs_client *pos, *n, *prev = NULL;
319 struct nfs4_setclientid_res clid = {
320 .clientid = new->cl_clientid,
321 .confirm = new->cl_confirm,
322 };
323 int status;
324
325 spin_lock(&nn->nfs_client_lock);
326 list_for_each_entry_safe(pos, n, &nn->nfs_client_list, cl_share_link) {
327 /* If "pos" isn't marked ready, we can't trust the
328 * remaining fields in "pos" */
329 if (pos->cl_cons_state < NFS_CS_READY)
330 continue;
331
332 if (pos->rpc_ops != new->rpc_ops)
333 continue;
334
335 if (pos->cl_proto != new->cl_proto)
336 continue;
337
338 if (pos->cl_minorversion != new->cl_minorversion)
339 continue;
340
341 if (pos->cl_clientid != new->cl_clientid)
342 continue;
343
344 atomic_inc(&pos->cl_count);
345 spin_unlock(&nn->nfs_client_lock);
346
347 if (prev)
348 nfs_put_client(prev);
349
350 status = nfs4_proc_setclientid_confirm(pos, &clid, cred);
351 if (status == 0) {
352 nfs4_swap_callback_idents(pos, new);
353
354 nfs_put_client(pos);
355 *result = pos;
356 dprintk("NFS: <-- %s using nfs_client = %p ({%d})\n",
357 __func__, pos, atomic_read(&pos->cl_count));
358 return 0;
359 }
360 if (status != -NFS4ERR_STALE_CLIENTID) {
361 nfs_put_client(pos);
362 dprintk("NFS: <-- %s status = %d, no result\n",
363 __func__, status);
364 return status;
365 }
366
367 spin_lock(&nn->nfs_client_lock);
368 prev = pos;
369 }
370
371 /*
372 * No matching nfs_client found. This should be impossible,
373 * because the new nfs_client has already been added to
374 * nfs_client_list by nfs_get_client().
375 *
376 * Don't BUG(), since the caller is holding a mutex.
377 */
378 if (prev)
379 nfs_put_client(prev);
380 spin_unlock(&nn->nfs_client_lock);
381 pr_err("NFS: %s Error: no matching nfs_client found\n", __func__);
382 return -NFS4ERR_STALE_CLIENTID;
383}
384
385#ifdef CONFIG_NFS_V4_1
386/*
387 * Returns true if the server owners match
388 */
389static bool
390nfs4_match_serverowners(struct nfs_client *a, struct nfs_client *b)
391{
392 struct nfs41_server_owner *o1 = a->cl_serverowner;
393 struct nfs41_server_owner *o2 = b->cl_serverowner;
394
395 if (o1->minor_id != o2->minor_id) {
396 dprintk("NFS: --> %s server owner minor IDs do not match\n",
397 __func__);
398 return false;
399 }
400
401 if (o1->major_id_sz != o2->major_id_sz)
402 goto out_major_mismatch;
403 if (memcmp(o1->major_id, o2->major_id, o1->major_id_sz) != 0)
404 goto out_major_mismatch;
405
406 dprintk("NFS: --> %s server owners match\n", __func__);
407 return true;
408
409out_major_mismatch:
410 dprintk("NFS: --> %s server owner major IDs do not match\n",
411 __func__);
412 return false;
413}
414
415/**
416 * nfs41_walk_client_list - Find nfs_client that matches a client/server owner
417 *
418 * @new: nfs_client with client ID to test
419 * @result: OUT: found nfs_client, or new
420 * @cred: credential to use for trunking test
421 *
422 * Returns zero, a negative errno, or a negative NFS4ERR status.
423 * If zero is returned, an nfs_client pointer is planted in "result."
424 *
425 * NB: nfs41_walk_client_list() relies on the new nfs_client being
426 * the last nfs_client on the list.
427 */
428int nfs41_walk_client_list(struct nfs_client *new,
429 struct nfs_client **result,
430 struct rpc_cred *cred)
431{
432 struct nfs_net *nn = net_generic(new->cl_net, nfs_net_id);
433 struct nfs_client *pos, *n, *prev = NULL;
434 int error;
435
436 spin_lock(&nn->nfs_client_lock);
437 list_for_each_entry_safe(pos, n, &nn->nfs_client_list, cl_share_link) {
438 /* If "pos" isn't marked ready, we can't trust the
439 * remaining fields in "pos", especially the client
440 * ID and serverowner fields. Wait for CREATE_SESSION
441 * to finish. */
442 if (pos->cl_cons_state < NFS_CS_READY) {
443 atomic_inc(&pos->cl_count);
444 spin_unlock(&nn->nfs_client_lock);
445
446 if (prev)
447 nfs_put_client(prev);
448 prev = pos;
449
450 error = nfs_wait_client_init_complete(pos);
451 if (error < 0) {
452 nfs_put_client(pos);
453 continue;
454 }
455
456 spin_lock(&nn->nfs_client_lock);
457 }
458
459 if (pos->rpc_ops != new->rpc_ops)
460 continue;
461
462 if (pos->cl_proto != new->cl_proto)
463 continue;
464
465 if (pos->cl_minorversion != new->cl_minorversion)
466 continue;
467
468 if (!nfs4_match_clientids(pos, new))
469 continue;
470
471 if (!nfs4_match_serverowners(pos, new))
472 continue;
473
474 spin_unlock(&nn->nfs_client_lock);
475 dprintk("NFS: <-- %s using nfs_client = %p ({%d})\n",
476 __func__, pos, atomic_read(&pos->cl_count));
477
478 *result = pos;
479 return 0;
480 }
481
482 /*
483 * No matching nfs_client found. This should be impossible,
484 * because the new nfs_client has already been added to
485 * nfs_client_list by nfs_get_client().
486 *
487 * Don't BUG(), since the caller is holding a mutex.
488 */
489 spin_unlock(&nn->nfs_client_lock);
490 pr_err("NFS: %s Error: no matching nfs_client found\n", __func__);
491 return -NFS4ERR_STALE_CLIENTID;
492}
493#endif /* CONFIG_NFS_V4_1 */
494
242static void nfs4_destroy_server(struct nfs_server *server) 495static void nfs4_destroy_server(struct nfs_server *server)
243{ 496{
244 nfs_server_return_all_delegations(server); 497 nfs_server_return_all_delegations(server);