Re: [PATCH 18/20] NFS: Detect NFSv4 server trunking when mounting

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Mon, 2012-04-23 at 16:55 -0400, Chuck Lever wrote:
> Currently the Linux NFS client waits to perform a SETCLIENTID until
> just before an application wants to open a file.  Quite a bit of
> activity can occur before any state is needed.
> 
> If the client cares about server trunking, however, no NFSv4
> operations can proceed until the client determines who it is talking
> to.  Thus server IP trunking detection 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 an init_client()
> method in this case.
> 
> Thus, nfs4_init_client() can establish a fresh client ID, and perform
> trunking detection with it.  The exact process for detecting trunking
> is different for NFSv4.0 and NFSv4.1, so a minorversion-specific
> init_client callout is introduced.
> 
> Signed-off-by: Chuck Lever <chuck.lever@xxxxxxxxxx>
> ---
> 
>  fs/nfs/client.c    |  223 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  fs/nfs/internal.h  |    6 +
>  fs/nfs/nfs4_fs.h   |    7 ++
>  fs/nfs/nfs4proc.c  |    2 
>  fs/nfs/nfs4state.c |  131 ++++++++++++++++++++++++++++++-
>  5 files changed, 367 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/nfs/client.c b/fs/nfs/client.c
> index 920abbc..7330673 100644
> --- a/fs/nfs/client.c
> +++ b/fs/nfs/client.c
> @@ -566,7 +566,8 @@ nfs_get_client(const struct nfs_client_initdata *cl_init,
>  			return nfs_found_client(cl_init, clp);
>  		}
>  		if (new) {
> -			list_add(&new->cl_share_link, &nn->nfs_client_list);
> +			list_add_tail(&new->cl_share_link,
> +					&nn->nfs_client_list);
>  			spin_unlock(&nn->nfs_client_lock);
>  			new->cl_flags = cl_init->init_flags;
>  			return cl_init->rpc_ops->init_client(new,
> @@ -584,6 +585,210 @@ nfs_get_client(const struct nfs_client_initdata *cl_init,
>  	return new;
>  }
>  
> +#ifdef CONFIG_NFS_V4
> +/*
> + * Returns true if the client IDs match
> + */
> +static bool
> +nfs4_match_clientids(struct nfs_client *a, struct nfs_client *b)
> +{
> +	if (a->cl_clientid != b->cl_clientid) {
> +		dprintk("NFS: --> %s client ID %llx does not match %llx\n",
> +			__func__, a->cl_clientid, b->cl_clientid);
> +		return false;
> +	}
> +	dprintk("NFS: --> %s client ID %llx matches %llx\n",
> +		__func__, a->cl_clientid, b->cl_clientid);
> +	return true;
> +}
> +
> +/**
> + * nfs40_walk_client_list - Find server that recognizes a client ID
> + *
> + * @new: nfs_client with client ID to test
> + * @result: OUT: found nfs_client, or new
> + * @cred: credential to use for trunking test
> + *
> + * Returns NFS4_OK, a negative errno, or a negative NFS4ERR status.
> + * If NFS4_OK is returned, an nfs_client pointer is planted in "result."
> + *
> + * NB: nfs40_walk_client_list() relies on the new nfs_client being
> + *     the last nfs_client on the list.
> + */
> +int nfs40_walk_client_list(struct nfs_client *new,
> +			   struct nfs_client **result,
> +			   struct rpc_cred *cred)
> +{
> +	struct nfs_net *nn = net_generic(new->cl_net, nfs_net_id);
> +	struct nfs_client *pos, *prev = NULL;
> +	struct nfs4_setclientid_res clid = {
> +		.clientid	= new->cl_clientid,
> +		.confirm	= new->cl_confirm,
> +	};
> +	int status;
> +
> +	dprintk("NFS: --> %s nfs_client = %p\n", __func__, new);
> +
> +	spin_lock(&nn->nfs_client_lock);
> +
> +	list_for_each_entry(pos, &nn->nfs_client_list, cl_share_link) {
> +		if (pos->cl_cons_state < 0)
> +			continue;
> +
> +		if (pos->rpc_ops != new->rpc_ops)
> +			continue;
> +
> +		if (pos->cl_proto != new->cl_proto)
> +			continue;
> +
> +		if (pos->cl_minorversion != new->cl_minorversion)
> +			continue;
> +
> +		dprintk("NFS: --> %s comparing %llx and %llx\n", __func__,
> +			new->cl_clientid, pos->cl_clientid);
> +		if (pos->cl_clientid != new->cl_clientid)
> +			continue;
> +
> +		atomic_inc(&pos->cl_count);
> +		dprintk("%s nfs_client = %p ({%d})\n",
> +			__func__, pos, atomic_read(&pos->cl_count));
> +		spin_unlock(&nn->nfs_client_lock);
> +
> +		dprintk("NFS: --> %s confirming %llx\n",
> +			__func__, new->cl_clientid);
> +
> +		if (prev)
> +			nfs_put_client(prev);
> +
> +		status = nfs4_proc_setclientid_confirm(pos, &clid, cred);

How are you protecting against NFS4CLNT_PURGE_STATE?

> +		if (status == NFS4_OK) {

status == 0

> +			/* The new nfs_client doesn't need the extra
> +			 * cl_count bump. */
> +			nfs_put_client(pos);
> +			*result = pos;
> +			dprintk("NFS: <-- %s using nfs_client = %p ({%d})\n",
> +				__func__, pos, atomic_read(&pos->cl_count));
> +			return NFS4_OK;

return 0

> +		}
> +		if (status != NFS4ERR_STALE_CLIENTID) {

All error values are _negative_ integers.

> +			nfs_put_client(pos);
> +			dprintk("NFS: <-- %s status = %d, no result\n",
> +				__func__, status);
> +			return status;
> +		}
> +
> +		spin_lock(&nn->nfs_client_lock);
> +		prev = pos;
> +	}
> +
> +	/*
> +	 * No matching nfs_client found.  This should be impossible,
> +	 * because the new nfs_client has already been added to
> +	 * nfs_client_list by nfs_get_client().
> +	 *
> +	 * Don't BUG(), since the caller is holding a mutex.
> +	 */
> +	spin_unlock(&nn->nfs_client_lock);
> +	printk(KERN_ERR "NFS: %s Error: no matching nfs_client found\n",
> +		__func__);
> +	return NFS4ERR_STALE_CLIENTID;

-NFS4ERR_STALE_CLIENTID...

> +}
> +
> +#ifdef CONFIG_NFS_V4_1
> +/*
> + * Returns true if the server owners match
> + */
> +static bool
> +nfs4_match_serverowners(struct nfs_client *a, struct nfs_client *b)
> +{
> +	struct nfs41_server_owner *o1 = a->cl_serverowner;
> +	struct nfs41_server_owner *o2 = b->cl_serverowner;
> +
> +	if (o1->minor_id != o2->minor_id) {
> +		dprintk("NFS: --> %s server owner minor IDs do not match\n",
> +			__func__);
> +		return false;
> +	}
> +
> +	if (o1->major_id_sz != o2->major_id_sz)
> +		goto out_major_mismatch;
> +	if (memcmp(o1->major_id, o2->major_id, o1->major_id_sz) != 0)
> +		goto out_major_mismatch;
> +
> +	dprintk("NFS: --> %s server owners match\n", __func__);
> +	return true;
> +
> +out_major_mismatch:
> +	dprintk("NFS: --> %s server owner major IDs do not match\n",
> +		__func__);
> +	return false;
> +}
> +
> +/**
> + * nfs41_walk_client_list - Find nfs_client that matches a client/server owner
> + *
> + * @new: nfs_client with client ID to test
> + * @result: OUT: found nfs_client, or new
> + * @cred: credential to use for trunking test
> + *
> + * Returns NFS4_OK, a negative errno, or a negative NFS4ERR status.
> + * If NFS4_OK is returned, an nfs_client pointer is planted in "result."
> + *
> + * NB: nfs41_walk_client_list() relies on the new nfs_client being
> + *     the last nfs_client on the list.
> + */
> +int nfs41_walk_client_list(struct nfs_client *new,
> +			   struct nfs_client **result,
> +			   struct rpc_cred *cred)
> +{
> +	struct nfs_net *nn = net_generic(new->cl_net, nfs_net_id);
> +	struct nfs_client *pos;
> +
> +	dprintk("NFS: --> %s nfs_client = %p\n", __func__, new);
> +
> +	spin_lock(&nn->nfs_client_lock);
> +
> +	list_for_each_entry(pos, &nn->nfs_client_list, cl_share_link) {
> +		if (pos->cl_cons_state < 0)
> +			continue;
> +
> +		if (pos->rpc_ops != new->rpc_ops)
> +			continue;
> +
> +		if (pos->cl_proto != new->cl_proto)
> +			continue;
> +
> +		if (pos->cl_minorversion != new->cl_minorversion)
> +			continue;
> +
> +		if (!nfs4_match_clientids(pos, new))
> +			continue;
> +
> +		if (!nfs4_match_serverowners(pos, new))
> +			continue;
> +
> +		atomic_inc(&pos->cl_count);
> +		*result = pos;
> +		dprintk("NFS: <-- %s using nfs_client = %p ({%d})\n",
> +			__func__, pos, atomic_read(&pos->cl_count));
> +		return NFS4_OK;

0

> +	}
> +
> +	/*
> +	 * No matching nfs_client found.  This should be impossible,
> +	 * because the new nfs_client has already been added to
> +	 * nfs_client_list by nfs_get_client().
> +	 *
> +	 * Don't BUG(), since the caller is holding a mutex.
> +	 */
> +	spin_unlock(&nn->nfs_client_lock);
> +	printk(KERN_ERR "NFS: %s Error: no matching nfs_client found\n",
> +		__func__);
> +	return NFS4ERR_STALE_CLIENTID;

-NFS4ERR_STALE_CLIENTID

> +}
> +#endif	/* CONFIG_NFS_V4_1 */
> +#endif	/* CONFIG_NFS_V4 */
> +
>  /*
>   * Mark a server as ready or failed
>   */
> @@ -1350,6 +1555,7 @@ struct nfs_client *nfs4_init_client(struct nfs_client *clp,
>  				    rpc_authflavor_t authflavour)
>  {
>  	char buf[INET6_ADDRSTRLEN + 1];
> +	struct nfs_client *old;
>  	int error;
>  
>  	if (clp->cl_cons_state == NFS_CS_READY) {
> @@ -1395,6 +1601,21 @@ struct nfs_client *nfs4_init_client(struct nfs_client *clp,
>  
>  	if (!nfs4_has_session(clp))
>  		nfs_mark_client_ready(clp, NFS_CS_READY);
> +
> +	error = nfs4_detect_trunking(clp, &old);
> +	if (error < 0)
> +		goto error;
> +	if (clp != old) {
> +		nfs_mark_client_ready(clp, NFS_CS_READY);
> +		nfs_put_client(clp);
> +		dprintk("<-- %s() returning %p instead of %p\n",
> +			__func__, old, clp);
> +		clp = old;
> +		atomic_inc(&clp->cl_count);
> +		dprintk("NFS: <-- %s using nfs_client = %p ({%d})\n",
> +			__func__, clp, atomic_read(&clp->cl_count));
> +	}
> +
>  	return clp;
>  
>  error:
> diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
> index 315dc86..85888f6 100644
> --- a/fs/nfs/internal.h
> +++ b/fs/nfs/internal.h
> @@ -162,6 +162,12 @@ extern struct nfs_client *nfs4_init_client(struct nfs_client *clp,
>  				const struct rpc_timeout *timeparms,
>  				const char *ip_addr,
>  				rpc_authflavor_t authflavour);
> +extern int nfs40_walk_client_list(struct nfs_client *clp,
> +				struct nfs_client **result,
> +				struct rpc_cred *cred);
> +extern int nfs41_walk_client_list(struct nfs_client *clp,
> +				struct nfs_client **result,
> +				struct rpc_cred *cred);
>  extern struct nfs_server *nfs_create_server(
>  					const struct nfs_parsed_mount_data *,
>  					struct nfs_fh *);
> diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
> index 2953f2c..ba13986 100644
> --- a/fs/nfs/nfs4_fs.h
> +++ b/fs/nfs/nfs4_fs.h
> @@ -190,6 +190,8 @@ struct nfs4_state_recovery_ops {
>  	int (*establish_clid)(struct nfs_client *, struct rpc_cred *);
>  	struct rpc_cred * (*get_clid_cred)(struct nfs_client *);
>  	int (*reclaim_complete)(struct nfs_client *);
> +	int (*detect_trunking)(struct nfs_client *, struct nfs_client **,
> +		struct rpc_cred *);
>  };
>  
>  struct nfs4_state_maintenance_ops {
> @@ -297,9 +299,14 @@ extern void nfs4_renew_state(struct work_struct *);
>  /* nfs4state.c */
>  struct rpc_cred *nfs4_get_setclientid_cred(struct nfs_client *clp);
>  struct rpc_cred *nfs4_get_renew_cred_locked(struct nfs_client *clp);
> +int nfs4_detect_trunking(struct nfs_client *clp, struct nfs_client **);
> +int nfs40_detect_trunking(struct nfs_client *clp, struct nfs_client **,
> +			struct rpc_cred *);
>  #if defined(CONFIG_NFS_V4_1)
>  struct rpc_cred *nfs4_get_machine_cred_locked(struct nfs_client *clp);
>  struct rpc_cred *nfs4_get_exchange_id_cred(struct nfs_client *clp);
> +int nfs41_detect_trunking(struct nfs_client *clp, struct nfs_client **,
> +			struct rpc_cred *);
>  extern void nfs4_schedule_session_recovery(struct nfs4_session *);
>  #else
>  static inline void nfs4_schedule_session_recovery(struct nfs4_session *session)
> diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
> index 3fd9944..00b5d02 100644
> --- a/fs/nfs/nfs4proc.c
> +++ b/fs/nfs/nfs4proc.c
> @@ -6453,6 +6453,7 @@ static const struct nfs4_state_recovery_ops nfs40_reboot_recovery_ops = {
>  	.recover_lock	= nfs4_lock_reclaim,
>  	.establish_clid = nfs4_init_clientid,
>  	.get_clid_cred	= nfs4_get_setclientid_cred,
> +	.detect_trunking = nfs40_detect_trunking,
>  };
>  
>  #if defined(CONFIG_NFS_V4_1)
> @@ -6464,6 +6465,7 @@ static const struct nfs4_state_recovery_ops nfs41_reboot_recovery_ops = {
>  	.establish_clid = nfs41_init_clientid,
>  	.get_clid_cred	= nfs4_get_exchange_id_cred,
>  	.reclaim_complete = nfs41_proc_reclaim_complete,
> +	.detect_trunking = nfs41_detect_trunking,
>  };
>  #endif /* CONFIG_NFS_V4_1 */
>  
> diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c
> index 6a1a305..df59951 100644
> --- a/fs/nfs/nfs4state.c
> +++ b/fs/nfs/nfs4state.c
> @@ -57,10 +57,12 @@
>  #include "internal.h"
>  #include "pnfs.h"
>  
> +#define NFSDBG_FACILITY		NFSDBG_CLIENT

Most of the stuff in nfs4state.c is dealing with NFSv4-specific state.
It does not make sense to lump that in with the nfs_client debugging.

> +
>  #define OPENOWNER_POOL_SIZE	8
>  
>  const nfs4_stateid zero_stateid;
> -
> +static DEFINE_MUTEX(nfs_clid_init_mutex);
>  static LIST_HEAD(nfs4_clientid_list);
>  
>  int nfs4_init_clientid(struct nfs_client *clp, struct rpc_cred *cred)
> @@ -94,6 +96,47 @@ out:
>  	return status;
>  }
>  
> +/**
> + * nfs40_detect_trunking - Detect server IP address trunking (mv0)
> + *
> + * @clp: nfs_client under test
> + * @result: OUT: found nfs_client, or clp
> + * @cred: credential to use for trunking test
> + *
> + * Returns NFS4_OK, a negative errno, or a negative NFS4ERR status.
> + * If NFS4_OK is returned, an nfs_client pointer is planted in
> + * "result".
> + */
> +int nfs40_detect_trunking(struct nfs_client *clp, struct nfs_client **result,
> +			  struct rpc_cred *cred)
> +{
> +	struct nfs4_setclientid_res clid = {
> +		.clientid = clp->cl_clientid,
> +		.confirm = clp->cl_confirm,
> +	};
> +	unsigned short port;
> +	int status;
> +
> +	port = nfs_callback_tcpport;
> +	if (clp->cl_addr.ss_family == AF_INET6)
> +		port = nfs_callback_tcpport6;
> +
> +	status = nfs4_proc_setclientid(clp, NFS4_CALLBACK, port, cred, &clid);
> +	if (status != NFS4_OK)
> +		goto out;
> +	clp->cl_clientid = clid.clientid;
> +	clp->cl_confirm = clid.confirm;
> +
> +	status = nfs40_walk_client_list(clp, result, cred);
> +	if (status != NFS4_OK) {
> +		set_bit(NFS4CLNT_LEASE_CONFIRM, &clp->cl_state);
> +		nfs4_schedule_state_renewal(*result);
> +	}
> +
> +out:
> +	return status;
> +}
> +
>  struct rpc_cred *nfs4_get_machine_cred_locked(struct nfs_client *clp)
>  {
>  	struct rpc_cred *cred = NULL;
> @@ -264,6 +307,44 @@ out:
>  	return status;
>  }
>  
> +/**
> + * nfs41_detect_trunking - Detect server IP address trunking (mv1)
> + *
> + * @clp: nfs_client under test
> + * @result: OUT: found nfs_client, or clp
> + * @cred: credential to use for trunking test
> + *
> + * Returns NFS4_OK, a negative errno, or a negative NFS4ERR status.
> + * If NFS4_OK is returned, an nfs_client pointer is planted in
> + * "result".
> + */
> +int nfs41_detect_trunking(struct nfs_client *clp, struct nfs_client **result,
> +			  struct rpc_cred *cred)
> +{
> +	struct nfs_client *trunked;
> +	int status;
> +
> +	nfs4_begin_drain_session(clp);
> +	status = nfs4_proc_exchange_id(clp, cred);
> +	if (status != NFS4_OK)
> +		goto out;
> +
> +	status = nfs41_walk_client_list(clp, &trunked, cred);
> +	if (status != NFS4_OK)
> +		goto out;
> +
> +	set_bit(NFS4CLNT_LEASE_CONFIRM, &trunked->cl_state);
> +	status = nfs4_proc_create_session(trunked);
> +	if (status != NFS4_OK)
> +		goto out;
> +	clear_bit(NFS4CLNT_LEASE_CONFIRM, &trunked->cl_state);
> +	nfs41_setup_state_renewal(trunked);
> +	nfs_mark_client_ready(trunked, NFS_CS_READY);
> +	*result = trunked;
> +out:
> +	return status;
> +}
> +
>  struct rpc_cred *nfs4_get_exchange_id_cred(struct nfs_client *clp)
>  {
>  	struct rpc_cred *cred;
> @@ -1579,6 +1660,8 @@ static int nfs4_reclaim_lease(struct nfs_client *clp)
>  	rpc_authflavor_t flavors[NFS_MAX_SECFLAVORS];
>  	int i, len, status;
>  
> +	mutex_lock(&nfs_clid_init_mutex);
> +
>  	i = 0;
>  	len = gss_mech_list_pseudoflavors(flavors);
>  
> @@ -1613,6 +1696,52 @@ again:
>  			break;
>  		}
>  	}
> +
> +	mutex_unlock(&nfs_clid_init_mutex);
> +	return status;
> +}
> +
> +/**
> + * nfs4_detect_trunking - Detect server IP address trunking
> + *
> + * @clp: nfs_client under test
> + * @result: OUT: found nfs_client, or clp
> + *
> + * Returns NFS4_OK, a negative errno, or a negative NFS4ERR status.
> + * If NFS4_OK is returned, an nfs_client pointer is planted in
> + * "result".
> + */
> +int nfs4_detect_trunking(struct nfs_client *clp,
> +			 struct nfs_client **result)
> +{
> +	const struct nfs4_state_recovery_ops *ops =
> +				clp->cl_mvops->reboot_recovery_ops;
> +	struct rpc_cred *cred;
> +	int status;
> +
> +	dprintk("NFS: <-- %s nfs_client = %p\n", __func__, clp);
> +	mutex_lock(&nfs_clid_init_mutex);
> +
> +	status = -ENOENT;
> +	cred = ops->get_clid_cred(clp);
> +	if (cred != NULL) {
> +		status = ops->detect_trunking(clp, result, cred);
> +		put_rpccred(cred);
> +		/* Handle case where the user hasn't set up machine creds */
> +		if (status == -EACCES && cred == clp->cl_machine_cred) {
> +			nfs4_clear_machine_cred(clp);
> +			status = -EAGAIN;
> +		}
> +		if (status == -NFS4ERR_MINOR_VERS_MISMATCH)
> +			status = -EPROTONOSUPPORT;
> +	}
> +
> +	mutex_unlock(&nfs_clid_init_mutex);
> +	if (status == NFS4_OK) {
> +		clear_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
> +		dprintk("NFS: <-- %s result = %p\n", __func__, *result);
> +	} else
> +		dprintk("NFS: <-- %s status = %d\n", __func__, status);
>  	return status;
>  }
>  
> 

-- 
Trond Myklebust
Linux NFS client maintainer

NetApp
Trond.Myklebust@xxxxxxxxxx
www.netapp.com

��.n��������+%������w��{.n�����{��w���jg��������ݢj����G�������j:+v���w�m������w�������h�����٥



[Index of Archives]     [Linux Filesystem Development]     [Linux USB Development]     [Linux Media Development]     [Video for Linux]     [Linux NILFS]     [Linux Audio Users]     [Yosemite Info]     [Linux SCSI]

  Powered by Linux