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