// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com)
*/ #include <linux/module.h> #include <linux/nfs_fs.h> #include <linux/nfs_mount.h> #include <linux/sunrpc/addr.h> #include <linux/sunrpc/auth.h> #include <linux/sunrpc/xprt.h> #include <linux/sunrpc/bc_xprt.h> #include <linux/sunrpc/rpc_pipe_fs.h> #include <net/handshake.h> #include"internal.h" #include"callback.h" #include"delegation.h" #include"nfs4session.h" #include"nfs4idmap.h" #include"pnfs.h" #include"netns.h" #include"sysfs.h"
#define NFSDBG_FACILITY NFSDBG_CLIENT
/* * Get a unique NFSv4.0 callback identifier which will be used * by the V4.0 callback service to lookup the nfs_client struct
*/ staticint nfs_get_cb_ident_idr(struct nfs_client *clp, int minorversion)
{ int ret = 0; struct nfs_net *nn = net_generic(clp->cl_net, nfs_net_id);
if (clp->rpc_ops->version != 4 || minorversion != 0) return ret;
idr_preload(GFP_KERNEL);
spin_lock(&nn->nfs_client_lock);
ret = idr_alloc(&nn->cb_ident_idr, clp, 1, 0, GFP_NOWAIT); if (ret >= 0)
clp->cl_cb_ident = ret;
spin_unlock(&nn->nfs_client_lock);
idr_preload_end(); return ret < 0 ? ret : 0;
}
#ifdef CONFIG_NFS_V4_1 /* * Per auth flavor data server rpc clients
*/ struct nfs4_ds_server { struct list_head list; /* ds_clp->cl_ds_clients */ struct rpc_clnt *rpc_clnt;
};
/** * nfs4_find_ds_client - Common lookup case for DS I/O * @ds_clp: pointer to the DS's nfs_client * @flavor: rpc auth flavour to match
*/ staticstruct nfs4_ds_server *
nfs4_find_ds_client(struct nfs_client *ds_clp, rpc_authflavor_t flavor)
{ struct nfs4_ds_server *dss;
if (cl_init->minorversion != 0)
__set_bit(NFS_CS_INFINITE_SLOTS, &clp->cl_flags);
__set_bit(NFS_CS_DISCRTRY, &clp->cl_flags);
__set_bit(NFS_CS_NO_RETRANS_TIMEOUT, &clp->cl_flags); if (test_bit(NFS_CS_PNFS, &cl_init->init_flags))
__set_bit(NFS_CS_PNFS, &clp->cl_flags); if (test_bit(NFS_CS_NETUNREACH_FATAL, &cl_init->init_flags))
__set_bit(NFS_CS_NETUNREACH_FATAL, &clp->cl_flags); /* * Set up the connection to the server before we add add to the * global list.
*/
err = nfs_create_rpc_client(clp, cl_init, RPC_AUTH_GSS_KRB5I); if (err == -EINVAL)
err = nfs_create_rpc_client(clp, cl_init, RPC_AUTH_UNIX); if (err < 0) goto error;
/* If no clientaddr= option was specified, find a usable cb address */ if (ip_addr == NULL) { struct sockaddr_storage cb_addr; struct sockaddr *sap = (struct sockaddr *)&cb_addr;
/** * 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(sizeof(*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;
}
#ifdefined(CONFIG_NFS_V4_1)
/** * nfs41_init_client - nfs_client initialization tasks for NFSv4.1+ * @clp: nfs_client to initialize * * Returns zero on success, or a negative errno if some error occurred.
*/ int nfs41_init_client(struct nfs_client *clp)
{ struct nfs4_session *session = NULL;
/* * Create the session and mark it expired. * When a SEQUENCE operation encounters the expired session * it will do session recovery to initialize it.
*/
session = nfs4_alloc_session(clp); if (!session) return -ENOMEM;
clp->cl_session = session;
/* * The create session reply races with the server back * channel probe. Mark the client NFS_CS_SESSION_INITING * so that the client back channel can find the * nfs_client struct
*/
nfs_mark_client_ready(clp, NFS_CS_SESSION_INITING); return 0;
}
#endif/* CONFIG_NFS_V4_1 */
/* * Initialize the minor version specific parts of an NFS4 client record
*/ staticint nfs4_init_client_minor_version(struct nfs_client *clp)
{ int ret;
ret = clp->cl_mvops->init_client(clp); if (ret) return ret; return nfs4_init_callback(clp);
}
/** * nfs4_init_client - Initialise an NFS4 client record * * @clp: nfs_client to initialise * @cl_init: pointer to nfs_client_initdata * * Returns pointer to an NFS client, or an ERR_PTR value.
*/ struct nfs_client *nfs4_init_client(struct nfs_client *clp, conststruct nfs_client_initdata *cl_init)
{ struct nfs_client *old; int error;
if (clp->cl_cons_state == NFS_CS_READY) /* the client is initialised already */ return clp;
error = nfs4_init_client_minor_version(clp); if (error < 0) goto error;
error = nfs4_discover_server_trunking(clp, &old); if (error < 0) goto error;
if (clp != old) {
clp->cl_preserve_clid = true; /* * Mark the client as having failed initialization so other * processes walking the nfs_client_list in nfs_match_client() * won't try to use it.
*/
nfs_mark_client_ready(clp, -EPERM); if (old->cl_mvops->session_trunk)
nfs4_add_trunk(clp, old);
}
clear_bit(NFS_CS_TSM_POSSIBLE, &clp->cl_flags);
nfs_put_client(clp); return old;
/* * 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.
*/ staticvoid nfs4_swap_callback_idents(struct nfs_client *keep, struct nfs_client *drop)
{ struct nfs_net *nn = net_generic(keep->cl_net, nfs_net_id); unsignedint save = keep->cl_cb_ident;
if (keep->cl_cb_ident == drop->cl_cb_ident) return;
if (pos->cl_minorversion != new->cl_minorversion) return 1;
/* If "pos" isn't marked ready, we can't trust the * remaining fields in "pos", especially the client * ID and serverowner fields. Wait for CREATE_SESSION
* to finish. */ if (pos->cl_cons_state > NFS_CS_READY) {
refcount_inc(&pos->cl_count);
spin_unlock(&nn->nfs_client_lock);
nfs_put_client(*prev);
*prev = pos;
status = nfs_wait_client_init_complete(pos);
spin_lock(&nn->nfs_client_lock);
if (status < 0) return status;
}
if (pos->cl_cons_state != NFS_CS_READY) return 1;
if (pos->cl_clientid != new->cl_clientid) return 1;
/* NFSv4.1 always uses the uniform string, however someone * might switch the uniquifier string on us.
*/ if (!nfs4_match_client_owner_id(pos, new)) return 1;
return 0;
}
/** * 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.
*/ int nfs40_walk_client_list(struct nfs_client *new, struct nfs_client **result, conststruct 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;
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;
}
/* No match found. The server lost our clientid */
out:
nfs_put_client(prev); return status;
}
#ifdef CONFIG_NFS_V4_1 /* * Returns true if the server major ids match
*/ bool
nfs4_check_serverowner_major_id(struct nfs41_server_owner *o1, struct nfs41_server_owner *o2)
{ if (o1->major_id_sz != o2->major_id_sz) returnfalse; return memcmp(o1->major_id, o2->major_id, o1->major_id_sz) == 0;
}
/* * Returns true if the server scopes match
*/ staticbool
nfs4_check_server_scope(struct nfs41_server_scope *s1, struct nfs41_server_scope *s2)
{ if (s1->server_scope_sz != s2->server_scope_sz) returnfalse; return memcmp(s1->server_scope, s2->server_scope,
s1->server_scope_sz) == 0;
}
/** * nfs4_detect_session_trunking - Checks for session trunking. * @clp: original mount nfs_client * @res: result structure from an exchange_id using the original mount * nfs_client with a new multi_addr transport * @xprt: pointer to the transport to add. * * Called after a successful EXCHANGE_ID on a multi-addr connection. * Upon success, add the transport. * * Returns zero on success, otherwise -EINVAL * * Note: since the exchange_id for the new multi_addr transport uses the * same nfs_client from the original mount, the cl_owner_id is reused, * so eir_clientowner is the same.
*/ int nfs4_detect_session_trunking(struct nfs_client *clp, struct nfs41_exchange_id_res *res, struct rpc_xprt *xprt)
{ /* Check eir_clientid */ if (clp->cl_clientid != res->clientid) goto out_err;
/* Check eir_server_owner so_major_id */ if (!nfs4_check_serverowner_major_id(clp->cl_serverowner,
res->server_owner)) goto out_err;
/** * nfs41_walk_client_list - Find nfs_client that matches a client/server owner * * @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: nfs41_walk_client_list() relies on the new nfs_client being * the last nfs_client on the list.
*/ int nfs41_walk_client_list(struct nfs_client *new, struct nfs_client **result, conststruct cred *cred)
{ struct nfs_net *nn = net_generic(new->cl_net, nfs_net_id); struct nfs_client *pos, *prev = NULL; int status = -NFS4ERR_STALE_CLIENTID;
status = nfs4_match_client(pos, new, &prev, nn); if (status < 0) goto out; if (status != 0) continue;
/* * Note that session trunking is just a special subcase of * client id trunking. In either case, we want to fall back * to using the existing nfs_client.
*/ if (!nfs4_check_serverowner_major_id(pos->cl_serverowner,
new->cl_serverowner)) continue;
found:
refcount_inc(&pos->cl_count);
*result = pos;
status = 0; break;
}
#ifdefined(CONFIG_NFS_V4_1) /* Common match routine for v4.0 and v4.1 callback services */ staticbool nfs4_cb_match_client(conststruct sockaddr *addr, struct nfs_client *clp, u32 minorversion)
{ struct sockaddr *clap = (struct sockaddr *)&clp->cl_addr;
/* Don't match clients that failed to initialise */ if (!(clp->cl_cons_state == NFS_CS_READY ||
clp->cl_cons_state == NFS_CS_SESSION_INITING)) returnfalse;
smp_rmb();
/* Match the version and minorversion */ if (clp->rpc_ops->version != 4 ||
clp->cl_minorversion != minorversion) returnfalse;
/* Match only the IP address, not the port number */ return rpc_cmp_addr(addr, clap);
}
/* * NFSv4.1 callback thread helper * For CB_COMPOUND calls, find a client by IP address, protocol version, * minorversion, and sessionID * * Returns NULL if no such client
*/ struct nfs_client *
nfs4_find_client_sessionid(struct net *net, conststruct sockaddr *addr, struct nfs4_sessionid *sid, u32 minorversion)
{ struct nfs_client *clp; struct nfs_net *nn = net_generic(net, nfs_net_id);
spin_lock(&nn->nfs_client_lock);
list_for_each_entry(clp, &nn->nfs_client_list, cl_share_link) { if (!nfs4_cb_match_client(addr, clp, minorversion)) continue;
if (!nfs4_has_session(clp)) continue;
/* Match sessionid*/ if (memcmp(clp->cl_session->sess_id.data,
sid->data, NFS4_MAX_SESSIONID_LEN) != 0) continue;
switch (cl_init->proto) { case XPRT_TRANSPORT_RDMA: case XPRT_TRANSPORT_TCP: case XPRT_TRANSPORT_TCP_TLS: break; default:
cl_init->nconnect = 0;
}
if (server->flags & NFS_MOUNT_NORESVPORT)
__set_bit(NFS_CS_NORESVPORT, &cl_init->init_flags); if (server->options & NFS_OPTION_MIGRATION)
__set_bit(NFS_CS_MIGRATION, &cl_init->init_flags); if (test_bit(NFS_MIG_TSM_POSSIBLE, &server->mig_status))
__set_bit(NFS_CS_TSM_POSSIBLE, &cl_init->init_flags);
server->port = rpc_get_port((struct sockaddr *)cl_init->addr);
if (server->flags & NFS_MOUNT_NETUNREACH_FATAL)
__set_bit(NFS_CS_NETUNREACH_FATAL, &cl_init->init_flags);
/* Allocate or find a client reference we can use */
clp = nfs_get_client(cl_init); if (IS_ERR(clp)) return PTR_ERR(clp);
if (server->nfs_client == clp) {
nfs_put_client(clp); return -ELOOP;
}
/* * Query for the lease time on clientid setup or renewal * * Note that this will be set on nfs_clients that were created * only for the DS role and did not set this bit, but now will * serve a dual role.
*/
set_bit(NFS_CS_CHECK_LEASE_TIME, &clp->cl_res_state);
/* * Set up a pNFS Data Server client. * * Return any existing nfs_client that matches server address,port,version * and minorversion. * * For a new nfs_client, use a soft mount (default), a low retrans and a * low timeout interval so that if a connection is lost, we retry through * the MDS.
*/ struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv, conststruct sockaddr_storage *ds_addr, int ds_addrlen, int ds_proto, unsignedint ds_timeo, unsignedint ds_retrans,
u32 minor_version)
{ struct rpc_timeout ds_timeout; struct nfs_client *mds_clp = mds_srv->nfs_client; struct nfs_client_initdata cl_init = {
.addr = ds_addr,
.addrlen = ds_addrlen,
.nodename = mds_clp->cl_rpcclient->cl_nodename,
.ip_addr = mds_clp->cl_ipaddr,
.nfs_mod = &nfs_v4,
.proto = ds_proto,
.minorversion = minor_version,
.net = mds_clp->cl_net,
.timeparms = &ds_timeout,
.cred = mds_srv->cred,
.xprtsec = {
.policy = RPC_XPRTSEC_NONE,
.cert_serial = TLS_NO_CERT,
.privkey_serial = TLS_NO_PRIVKEY,
},
}; char buf[INET6_ADDRSTRLEN + 1];
switch (ds_proto) { case XPRT_TRANSPORT_TCP_TLS: if (mds_srv->nfs_client->cl_xprtsec.policy != RPC_XPRTSEC_NONE)
cl_init.xprtsec = mds_srv->nfs_client->cl_xprtsec; else
ds_proto = XPRT_TRANSPORT_TCP;
fallthrough; case XPRT_TRANSPORT_RDMA: case XPRT_TRANSPORT_TCP: if (mds_clp->cl_nconnect > 1) {
cl_init.nconnect = mds_clp->cl_nconnect;
cl_init.max_connect = NFS_MAX_TRANSPORTS;
}
}
if (mds_srv->flags & NFS_MOUNT_NORESVPORT)
__set_bit(NFS_CS_NORESVPORT, &cl_init.init_flags); if (test_bit(NFS_CS_NETUNREACH_FATAL, &mds_clp->cl_flags))
__set_bit(NFS_CS_NETUNREACH_FATAL, &cl_init.init_flags);
__set_bit(NFS_CS_PNFS, &cl_init.init_flags);
cl_init.max_connect = NFS_MAX_TRANSPORTS; /* * Set an authflavor equual to the MDS value. Use the MDS nfs_client * cl_ipaddr so as to use the same EXCHANGE_ID co_ownerid as the MDS * (section 13.1 RFC 5661).
*/
nfs_init_timeout_values(&ds_timeout, ds_proto, ds_timeo, ds_retrans); return nfs_get_client(&cl_init);
}
EXPORT_SYMBOL_GPL(nfs4_set_ds_client);
/* * Session has been established, and the client marked ready. * Limit the mount rsize, wsize and dtsize using negotiated fore * channel attributes.
*/ staticvoid nfs4_session_limit_rwsize(struct nfs_server *server)
{ #ifdef CONFIG_NFS_V4_1 struct nfs4_session *sess;
u32 server_resp_sz;
u32 server_rqst_sz;
/* Initialise the client representation from the mount data */
server->flags = ctx->flags;
server->options = ctx->options;
server->auth_info = ctx->auth_info;
/* Use the first specified auth flavor. If this flavor isn't * allowed by the server, use the SECINFO path to try the
* other specified flavors */ if (ctx->auth_info.flavor_len >= 1)
ctx->selected_flavor = ctx->auth_info.flavors[0]; else
ctx->selected_flavor = RPC_AUTH_UNIX;
/* Get a client record */
error = nfs4_set_client(server, &cl_init); if (error < 0) return error;
if (ctx->rsize)
server->rsize = nfs_io_size(ctx->rsize, server->nfs_client->cl_proto); if (ctx->wsize)
server->wsize = nfs_io_size(ctx->wsize, server->nfs_client->cl_proto);
/* * Create a version 4 volume record * - keyed on server and FSID
*/ struct nfs_server *nfs4_create_server(struct fs_context *fc)
{ struct nfs_fs_context *ctx = nfs_fc2context(fc); struct nfs_server *server; bool auth_probe; int error;
server = nfs_alloc_server(); if (!server) return ERR_PTR(-ENOMEM);
server->cred = get_cred(fc->cred);
auth_probe = ctx->auth_info.flavor_len < 1;
/* set up the general RPC client */
error = nfs4_init_server(server, fc); if (error < 0) goto error;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.