/* SPDX-License-Identifier: GPL-2.0 */ #include #include "nfs4_fs.h" #include "nfs4session.h" #include "callback.h" #include "delegation.h" #include "internal.h" #include "netns.h" #include "nfs40.h" #define NFSDBG_FACILITY NFSDBG_CLIENT /* * SETCLIENTID just did a callback update with the callback ident in * "drop," but server trunking discovery claims "drop" and "keep" are * actually the same server. Swap the callback IDs so that "keep" * will continue to use the callback ident the server now knows about, * and so that "keep"'s original callback ident is destroyed when * "drop" is freed. */ static void nfs4_swap_callback_idents(struct nfs_client *keep, struct nfs_client *drop) { struct nfs_net *nn = net_generic(keep->cl_net, nfs_net_id); unsigned int save = keep->cl_cb_ident; if (keep->cl_cb_ident == drop->cl_cb_ident) return; dprintk("%s: keeping callback ident %u and dropping ident %u\n", __func__, keep->cl_cb_ident, drop->cl_cb_ident); spin_lock(&nn->nfs_client_lock); idr_replace(&nn->cb_ident_idr, keep, drop->cl_cb_ident); keep->cl_cb_ident = drop->cl_cb_ident; idr_replace(&nn->cb_ident_idr, drop, save); drop->cl_cb_ident = save; spin_unlock(&nn->nfs_client_lock); } static bool nfs4_same_verifier(nfs4_verifier *v1, nfs4_verifier *v2) { return memcmp(v1->data, v2->data, sizeof(v1->data)) == 0; } void nfs40_shutdown_client(struct nfs_client *clp) { if (clp->cl_slot_tbl) { nfs4_shutdown_slot_table(clp->cl_slot_tbl); kfree(clp->cl_slot_tbl); } } /** * nfs40_init_client - nfs_client initialization tasks for NFSv4.0 * @clp: nfs_client to initialize * * Returns zero on success, or a negative errno if some error occurred. */ int nfs40_init_client(struct nfs_client *clp) { struct nfs4_slot_table *tbl; int ret; tbl = kzalloc_obj(*tbl, GFP_NOFS); if (tbl == NULL) return -ENOMEM; ret = nfs4_setup_slot_table(tbl, NFS4_MAX_SLOT_TABLE, "NFSv4.0 transport Slot table"); if (ret) { nfs4_shutdown_slot_table(tbl); kfree(tbl); return ret; } clp->cl_slot_tbl = tbl; return 0; } /* * nfs40_handle_cb_pathdown - return all delegations after NFS4ERR_CB_PATH_DOWN * @clp: client to process * * Set the NFS4CLNT_LEASE_EXPIRED state in order to force a * resend of the SETCLIENTID and hence re-establish the * callback channel. Then return all existing delegations. */ void nfs40_handle_cb_pathdown(struct nfs_client *clp) { set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state); nfs_expire_all_delegations(clp); dprintk("%s: handling CB_PATHDOWN recovery for server %s\n", __func__, clp->cl_hostname); } void nfs4_schedule_path_down_recovery(struct nfs_client *clp) { nfs40_handle_cb_pathdown(clp); nfs4_schedule_state_manager(clp); } /** * nfs40_walk_client_list - Find server that recognizes a client ID * * @new: nfs_client with client ID to test * @result: OUT: found nfs_client, or new * @cred: credential to use for trunking test * * Returns zero, a negative errno, or a negative NFS4ERR status. * If zero is returned, an nfs_client pointer is planted in "result." * * NB: nfs40_walk_client_list() relies on the new nfs_client being * the last nfs_client on the list. */ static int nfs40_walk_client_list(struct nfs_client *new, struct nfs_client **result, const struct cred *cred) { struct nfs_net *nn = net_generic(new->cl_net, nfs_net_id); struct nfs_client *pos, *prev = NULL; struct nfs4_setclientid_res clid = { .clientid = new->cl_clientid, .confirm = new->cl_confirm, }; int status = -NFS4ERR_STALE_CLIENTID; spin_lock(&nn->nfs_client_lock); list_for_each_entry(pos, &nn->nfs_client_list, cl_share_link) { if (pos == new) goto found; status = nfs4_match_client(pos, new, &prev, nn); if (status < 0) goto out_unlock; if (status != 0) continue; /* * We just sent a new SETCLIENTID, which should have * caused the server to return a new cl_confirm. So if * cl_confirm is the same, then this is a different * server that just returned the same cl_confirm by * coincidence: */ if ((new != pos) && nfs4_same_verifier(&pos->cl_confirm, &new->cl_confirm)) continue; /* * But if the cl_confirm's are different, then the only * way that a SETCLIENTID_CONFIRM to pos can succeed is * if new and pos point to the same server: */ found: refcount_inc(&pos->cl_count); spin_unlock(&nn->nfs_client_lock); nfs_put_client(prev); prev = pos; status = nfs4_proc_setclientid_confirm(pos, &clid, cred); switch (status) { case -NFS4ERR_STALE_CLIENTID: break; case 0: nfs4_swap_callback_idents(pos, new); pos->cl_confirm = new->cl_confirm; nfs_mark_client_ready(pos, NFS_CS_READY); prev = NULL; *result = pos; goto out; case -ERESTARTSYS: case -ETIMEDOUT: /* The callback path may have been inadvertently * changed. Schedule recovery! */ nfs4_schedule_path_down_recovery(pos); goto out; default: goto out; } spin_lock(&nn->nfs_client_lock); } out_unlock: spin_unlock(&nn->nfs_client_lock); /* No match found. The server lost our clientid */ out: nfs_put_client(prev); return status; } /** * nfs40_discover_server_trunking - Detect server IP address trunking (mv0) * * @clp: nfs_client under test * @result: OUT: found nfs_client, or clp * @cred: credential to use for trunking test * * Returns zero, a negative errno, or a negative NFS4ERR status. * If zero is returned, an nfs_client pointer is planted in * "result". * * Note: The returned client may not yet be marked ready. */ int nfs40_discover_server_trunking(struct nfs_client *clp, struct nfs_client **result, const struct cred *cred) { struct nfs4_setclientid_res clid = { .clientid = clp->cl_clientid, .confirm = clp->cl_confirm, }; struct nfs_net *nn = net_generic(clp->cl_net, nfs_net_id); unsigned short port; int status; port = nn->nfs_callback_tcpport; if (clp->cl_addr.ss_family == AF_INET6) port = nn->nfs_callback_tcpport6; status = nfs4_proc_setclientid(clp, NFS4_CALLBACK, port, cred, &clid); if (status != 0) goto out; clp->cl_clientid = clid.clientid; clp->cl_confirm = clid.confirm; status = nfs40_walk_client_list(clp, result, cred); if (status == 0) { /* Sustain the lease, even if it's empty. If the clientid4 * goes stale it's of no use for trunking discovery. */ nfs4_schedule_state_renewal(*result); /* If the client state need to recover, do it. */ if (clp->cl_state) nfs4_schedule_state_manager(clp); } out: return status; }