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 */ 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 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 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 */ 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 */ 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 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 */ 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 */ 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