xref: /linux/fs/nfs/nfs40client.c (revision 8934827db5403eae57d4537114a9ff88b0a8460f)
1c96c05fcSAnna Schumaker /* SPDX-License-Identifier: GPL-2.0 */
2c96c05fcSAnna Schumaker #include <linux/nfs_fs.h>
3c96c05fcSAnna Schumaker #include "nfs4_fs.h"
4c695ac2dSAnna Schumaker #include "nfs4session.h"
5c96c05fcSAnna Schumaker #include "callback.h"
6424a3a71SAnna Schumaker #include "delegation.h"
7c96c05fcSAnna Schumaker #include "internal.h"
8c96c05fcSAnna Schumaker #include "netns.h"
9c96c05fcSAnna Schumaker #include "nfs40.h"
10c96c05fcSAnna Schumaker 
11c96c05fcSAnna Schumaker #define NFSDBG_FACILITY		NFSDBG_CLIENT
12c96c05fcSAnna Schumaker 
13c96c05fcSAnna Schumaker /*
14c96c05fcSAnna Schumaker  * SETCLIENTID just did a callback update with the callback ident in
15c96c05fcSAnna Schumaker  * "drop," but server trunking discovery claims "drop" and "keep" are
16c96c05fcSAnna Schumaker  * actually the same server.  Swap the callback IDs so that "keep"
17c96c05fcSAnna Schumaker  * will continue to use the callback ident the server now knows about,
18c96c05fcSAnna Schumaker  * and so that "keep"'s original callback ident is destroyed when
19c96c05fcSAnna Schumaker  * "drop" is freed.
20c96c05fcSAnna Schumaker  */
nfs4_swap_callback_idents(struct nfs_client * keep,struct nfs_client * drop)21c96c05fcSAnna Schumaker static void nfs4_swap_callback_idents(struct nfs_client *keep,
22c96c05fcSAnna Schumaker 				      struct nfs_client *drop)
23c96c05fcSAnna Schumaker {
24c96c05fcSAnna Schumaker 	struct nfs_net *nn = net_generic(keep->cl_net, nfs_net_id);
25c96c05fcSAnna Schumaker 	unsigned int save = keep->cl_cb_ident;
26c96c05fcSAnna Schumaker 
27c96c05fcSAnna Schumaker 	if (keep->cl_cb_ident == drop->cl_cb_ident)
28c96c05fcSAnna Schumaker 		return;
29c96c05fcSAnna Schumaker 
30c96c05fcSAnna Schumaker 	dprintk("%s: keeping callback ident %u and dropping ident %u\n",
31c96c05fcSAnna Schumaker 		__func__, keep->cl_cb_ident, drop->cl_cb_ident);
32c96c05fcSAnna Schumaker 
33c96c05fcSAnna Schumaker 	spin_lock(&nn->nfs_client_lock);
34c96c05fcSAnna Schumaker 
35c96c05fcSAnna Schumaker 	idr_replace(&nn->cb_ident_idr, keep, drop->cl_cb_ident);
36c96c05fcSAnna Schumaker 	keep->cl_cb_ident = drop->cl_cb_ident;
37c96c05fcSAnna Schumaker 
38c96c05fcSAnna Schumaker 	idr_replace(&nn->cb_ident_idr, drop, save);
39c96c05fcSAnna Schumaker 	drop->cl_cb_ident = save;
40c96c05fcSAnna Schumaker 
41c96c05fcSAnna Schumaker 	spin_unlock(&nn->nfs_client_lock);
42c96c05fcSAnna Schumaker }
43c96c05fcSAnna Schumaker 
nfs4_same_verifier(nfs4_verifier * v1,nfs4_verifier * v2)44c96c05fcSAnna Schumaker static bool nfs4_same_verifier(nfs4_verifier *v1, nfs4_verifier *v2)
45c96c05fcSAnna Schumaker {
46c96c05fcSAnna Schumaker 	return memcmp(v1->data, v2->data, sizeof(v1->data)) == 0;
47c96c05fcSAnna Schumaker }
48c96c05fcSAnna Schumaker 
nfs40_shutdown_client(struct nfs_client * clp)49c695ac2dSAnna Schumaker void nfs40_shutdown_client(struct nfs_client *clp)
50c695ac2dSAnna Schumaker {
51c695ac2dSAnna Schumaker 	if (clp->cl_slot_tbl) {
52c695ac2dSAnna Schumaker 		nfs4_shutdown_slot_table(clp->cl_slot_tbl);
53c695ac2dSAnna Schumaker 		kfree(clp->cl_slot_tbl);
54c695ac2dSAnna Schumaker 	}
55c695ac2dSAnna Schumaker }
56c695ac2dSAnna Schumaker 
57c96c05fcSAnna Schumaker /**
58214359feSAnna Schumaker  * nfs40_init_client - nfs_client initialization tasks for NFSv4.0
59214359feSAnna Schumaker  * @clp: nfs_client to initialize
60214359feSAnna Schumaker  *
61214359feSAnna Schumaker  * Returns zero on success, or a negative errno if some error occurred.
62214359feSAnna Schumaker  */
nfs40_init_client(struct nfs_client * clp)63214359feSAnna Schumaker int nfs40_init_client(struct nfs_client *clp)
64214359feSAnna Schumaker {
65214359feSAnna Schumaker 	struct nfs4_slot_table *tbl;
66214359feSAnna Schumaker 	int ret;
67214359feSAnna Schumaker 
68*69050f8dSKees Cook 	tbl = kzalloc_obj(*tbl, GFP_NOFS);
69214359feSAnna Schumaker 	if (tbl == NULL)
70214359feSAnna Schumaker 		return -ENOMEM;
71214359feSAnna Schumaker 
72214359feSAnna Schumaker 	ret = nfs4_setup_slot_table(tbl, NFS4_MAX_SLOT_TABLE,
73214359feSAnna Schumaker 					"NFSv4.0 transport Slot table");
74214359feSAnna Schumaker 	if (ret) {
75214359feSAnna Schumaker 		nfs4_shutdown_slot_table(tbl);
76214359feSAnna Schumaker 		kfree(tbl);
77214359feSAnna Schumaker 		return ret;
78214359feSAnna Schumaker 	}
79214359feSAnna Schumaker 
80214359feSAnna Schumaker 	clp->cl_slot_tbl = tbl;
81214359feSAnna Schumaker 	return 0;
82214359feSAnna Schumaker }
83214359feSAnna Schumaker 
84424a3a71SAnna Schumaker /*
85424a3a71SAnna Schumaker  * nfs40_handle_cb_pathdown - return all delegations after NFS4ERR_CB_PATH_DOWN
86424a3a71SAnna Schumaker  * @clp: client to process
87424a3a71SAnna Schumaker  *
88424a3a71SAnna Schumaker  * Set the NFS4CLNT_LEASE_EXPIRED state in order to force a
89424a3a71SAnna Schumaker  * resend of the SETCLIENTID and hence re-establish the
90424a3a71SAnna Schumaker  * callback channel. Then return all existing delegations.
91424a3a71SAnna Schumaker  */
nfs40_handle_cb_pathdown(struct nfs_client * clp)92424a3a71SAnna Schumaker void nfs40_handle_cb_pathdown(struct nfs_client *clp)
93424a3a71SAnna Schumaker {
94424a3a71SAnna Schumaker 	set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
95424a3a71SAnna Schumaker 	nfs_expire_all_delegations(clp);
96424a3a71SAnna Schumaker 	dprintk("%s: handling CB_PATHDOWN recovery for server %s\n", __func__,
97424a3a71SAnna Schumaker 			clp->cl_hostname);
98424a3a71SAnna Schumaker }
99424a3a71SAnna Schumaker 
nfs4_schedule_path_down_recovery(struct nfs_client * clp)100424a3a71SAnna Schumaker void nfs4_schedule_path_down_recovery(struct nfs_client *clp)
101424a3a71SAnna Schumaker {
102424a3a71SAnna Schumaker 	nfs40_handle_cb_pathdown(clp);
103424a3a71SAnna Schumaker 	nfs4_schedule_state_manager(clp);
104424a3a71SAnna Schumaker }
105424a3a71SAnna Schumaker 
106214359feSAnna Schumaker /**
107c96c05fcSAnna Schumaker  * nfs40_walk_client_list - Find server that recognizes a client ID
108c96c05fcSAnna Schumaker  *
109c96c05fcSAnna Schumaker  * @new: nfs_client with client ID to test
110c96c05fcSAnna Schumaker  * @result: OUT: found nfs_client, or new
111c96c05fcSAnna Schumaker  * @cred: credential to use for trunking test
112c96c05fcSAnna Schumaker  *
113c96c05fcSAnna Schumaker  * Returns zero, a negative errno, or a negative NFS4ERR status.
114c96c05fcSAnna Schumaker  * If zero is returned, an nfs_client pointer is planted in "result."
115c96c05fcSAnna Schumaker  *
116c96c05fcSAnna Schumaker  * NB: nfs40_walk_client_list() relies on the new nfs_client being
117c96c05fcSAnna Schumaker  *     the last nfs_client on the list.
118c96c05fcSAnna Schumaker  */
nfs40_walk_client_list(struct nfs_client * new,struct nfs_client ** result,const struct cred * cred)119c96c05fcSAnna Schumaker static int nfs40_walk_client_list(struct nfs_client *new,
120c96c05fcSAnna Schumaker 				  struct nfs_client **result,
121c96c05fcSAnna Schumaker 				  const struct cred *cred)
122c96c05fcSAnna Schumaker {
123c96c05fcSAnna Schumaker 	struct nfs_net *nn = net_generic(new->cl_net, nfs_net_id);
124c96c05fcSAnna Schumaker 	struct nfs_client *pos, *prev = NULL;
125c96c05fcSAnna Schumaker 	struct nfs4_setclientid_res clid = {
126c96c05fcSAnna Schumaker 		.clientid	= new->cl_clientid,
127c96c05fcSAnna Schumaker 		.confirm	= new->cl_confirm,
128c96c05fcSAnna Schumaker 	};
129c96c05fcSAnna Schumaker 	int status = -NFS4ERR_STALE_CLIENTID;
130c96c05fcSAnna Schumaker 
131c96c05fcSAnna Schumaker 	spin_lock(&nn->nfs_client_lock);
132c96c05fcSAnna Schumaker 	list_for_each_entry(pos, &nn->nfs_client_list, cl_share_link) {
133c96c05fcSAnna Schumaker 
134c96c05fcSAnna Schumaker 		if (pos == new)
135c96c05fcSAnna Schumaker 			goto found;
136c96c05fcSAnna Schumaker 
137c96c05fcSAnna Schumaker 		status = nfs4_match_client(pos, new, &prev, nn);
138c96c05fcSAnna Schumaker 		if (status < 0)
139c96c05fcSAnna Schumaker 			goto out_unlock;
140c96c05fcSAnna Schumaker 		if (status != 0)
141c96c05fcSAnna Schumaker 			continue;
142c96c05fcSAnna Schumaker 		/*
143c96c05fcSAnna Schumaker 		 * We just sent a new SETCLIENTID, which should have
144c96c05fcSAnna Schumaker 		 * caused the server to return a new cl_confirm.  So if
145c96c05fcSAnna Schumaker 		 * cl_confirm is the same, then this is a different
146c96c05fcSAnna Schumaker 		 * server that just returned the same cl_confirm by
147c96c05fcSAnna Schumaker 		 * coincidence:
148c96c05fcSAnna Schumaker 		 */
149c96c05fcSAnna Schumaker 		if ((new != pos) && nfs4_same_verifier(&pos->cl_confirm,
150c96c05fcSAnna Schumaker 						       &new->cl_confirm))
151c96c05fcSAnna Schumaker 			continue;
152c96c05fcSAnna Schumaker 		/*
153c96c05fcSAnna Schumaker 		 * But if the cl_confirm's are different, then the only
154c96c05fcSAnna Schumaker 		 * way that a SETCLIENTID_CONFIRM to pos can succeed is
155c96c05fcSAnna Schumaker 		 * if new and pos point to the same server:
156c96c05fcSAnna Schumaker 		 */
157c96c05fcSAnna Schumaker found:
158c96c05fcSAnna Schumaker 		refcount_inc(&pos->cl_count);
159c96c05fcSAnna Schumaker 		spin_unlock(&nn->nfs_client_lock);
160c96c05fcSAnna Schumaker 
161c96c05fcSAnna Schumaker 		nfs_put_client(prev);
162c96c05fcSAnna Schumaker 		prev = pos;
163c96c05fcSAnna Schumaker 
164c96c05fcSAnna Schumaker 		status = nfs4_proc_setclientid_confirm(pos, &clid, cred);
165c96c05fcSAnna Schumaker 		switch (status) {
166c96c05fcSAnna Schumaker 		case -NFS4ERR_STALE_CLIENTID:
167c96c05fcSAnna Schumaker 			break;
168c96c05fcSAnna Schumaker 		case 0:
169c96c05fcSAnna Schumaker 			nfs4_swap_callback_idents(pos, new);
170c96c05fcSAnna Schumaker 			pos->cl_confirm = new->cl_confirm;
171c96c05fcSAnna Schumaker 			nfs_mark_client_ready(pos, NFS_CS_READY);
172c96c05fcSAnna Schumaker 
173c96c05fcSAnna Schumaker 			prev = NULL;
174c96c05fcSAnna Schumaker 			*result = pos;
175c96c05fcSAnna Schumaker 			goto out;
176c96c05fcSAnna Schumaker 		case -ERESTARTSYS:
177c96c05fcSAnna Schumaker 		case -ETIMEDOUT:
178c96c05fcSAnna Schumaker 			/* The callback path may have been inadvertently
179c96c05fcSAnna Schumaker 			 * changed. Schedule recovery!
180c96c05fcSAnna Schumaker 			 */
181c96c05fcSAnna Schumaker 			nfs4_schedule_path_down_recovery(pos);
182c96c05fcSAnna Schumaker 			goto out;
183c96c05fcSAnna Schumaker 		default:
184c96c05fcSAnna Schumaker 			goto out;
185c96c05fcSAnna Schumaker 		}
186c96c05fcSAnna Schumaker 
187c96c05fcSAnna Schumaker 		spin_lock(&nn->nfs_client_lock);
188c96c05fcSAnna Schumaker 	}
189c96c05fcSAnna Schumaker out_unlock:
190c96c05fcSAnna Schumaker 	spin_unlock(&nn->nfs_client_lock);
191c96c05fcSAnna Schumaker 
192c96c05fcSAnna Schumaker 	/* No match found. The server lost our clientid */
193c96c05fcSAnna Schumaker out:
194c96c05fcSAnna Schumaker 	nfs_put_client(prev);
195c96c05fcSAnna Schumaker 	return status;
196c96c05fcSAnna Schumaker }
197c96c05fcSAnna Schumaker 
198c96c05fcSAnna Schumaker /**
199c96c05fcSAnna Schumaker  * nfs40_discover_server_trunking - Detect server IP address trunking (mv0)
200c96c05fcSAnna Schumaker  *
201c96c05fcSAnna Schumaker  * @clp: nfs_client under test
202c96c05fcSAnna Schumaker  * @result: OUT: found nfs_client, or clp
203c96c05fcSAnna Schumaker  * @cred: credential to use for trunking test
204c96c05fcSAnna Schumaker  *
205c96c05fcSAnna Schumaker  * Returns zero, a negative errno, or a negative NFS4ERR status.
206c96c05fcSAnna Schumaker  * If zero is returned, an nfs_client pointer is planted in
207c96c05fcSAnna Schumaker  * "result".
208c96c05fcSAnna Schumaker  *
209c96c05fcSAnna Schumaker  * Note: The returned client may not yet be marked ready.
210c96c05fcSAnna Schumaker  */
nfs40_discover_server_trunking(struct nfs_client * clp,struct nfs_client ** result,const struct cred * cred)211c96c05fcSAnna Schumaker int nfs40_discover_server_trunking(struct nfs_client *clp,
212c96c05fcSAnna Schumaker 				   struct nfs_client **result,
213c96c05fcSAnna Schumaker 				   const struct cred *cred)
214c96c05fcSAnna Schumaker {
215c96c05fcSAnna Schumaker 	struct nfs4_setclientid_res clid = {
216c96c05fcSAnna Schumaker 		.clientid = clp->cl_clientid,
217c96c05fcSAnna Schumaker 		.confirm = clp->cl_confirm,
218c96c05fcSAnna Schumaker 	};
219c96c05fcSAnna Schumaker 	struct nfs_net *nn = net_generic(clp->cl_net, nfs_net_id);
220c96c05fcSAnna Schumaker 	unsigned short port;
221c96c05fcSAnna Schumaker 	int status;
222c96c05fcSAnna Schumaker 
223c96c05fcSAnna Schumaker 	port = nn->nfs_callback_tcpport;
224c96c05fcSAnna Schumaker 	if (clp->cl_addr.ss_family == AF_INET6)
225c96c05fcSAnna Schumaker 		port = nn->nfs_callback_tcpport6;
226c96c05fcSAnna Schumaker 
227c96c05fcSAnna Schumaker 	status = nfs4_proc_setclientid(clp, NFS4_CALLBACK, port, cred, &clid);
228c96c05fcSAnna Schumaker 	if (status != 0)
229c96c05fcSAnna Schumaker 		goto out;
230c96c05fcSAnna Schumaker 	clp->cl_clientid = clid.clientid;
231c96c05fcSAnna Schumaker 	clp->cl_confirm = clid.confirm;
232c96c05fcSAnna Schumaker 
233c96c05fcSAnna Schumaker 	status = nfs40_walk_client_list(clp, result, cred);
234c96c05fcSAnna Schumaker 	if (status == 0) {
235c96c05fcSAnna Schumaker 		/* Sustain the lease, even if it's empty.  If the clientid4
236c96c05fcSAnna Schumaker 		 * goes stale it's of no use for trunking discovery. */
237c96c05fcSAnna Schumaker 		nfs4_schedule_state_renewal(*result);
238c96c05fcSAnna Schumaker 
239c96c05fcSAnna Schumaker 		/* If the client state need to recover, do it. */
240c96c05fcSAnna Schumaker 		if (clp->cl_state)
241c96c05fcSAnna Schumaker 			nfs4_schedule_state_manager(clp);
242c96c05fcSAnna Schumaker 	}
243c96c05fcSAnna Schumaker out:
244c96c05fcSAnna Schumaker 	return status;
245c96c05fcSAnna Schumaker }
246