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