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