nfsv4 client: do the BindConnectionToSession as required

During a recent testing event, it was reported that the NFSv4.1/4.2
server erroneously bound the back channel to a new TCP connection.
RFC5661 specifies that the fore channel is implicitly bound to a
new TCP connection when an RPC with Sequence (almost any of them)
is done on it.  For the back channel to be bound to the new TCP
connection, an explicit BindConnectionToSession must be done as
the first RPC on the new connection.

Since new TCP connections are created by the "reconnect" layer
(sys/rpc/clnt_rc.c) of the krpc, this patch adds an optional
upcall done by the krpc whenever a new connection is created.
The patch also adds the specific upcall function that does a
BindConnectionToSession and configures the krpc to call it
when required.

This is necessary for correct interoperability with NFSv4.1/NFSv4.2
servers when the nfscbd daemon is running.

If doing NFSv4.1/NFSv4.2 mounts without this patch, it is
recommended that the nfscbd daemon not be running and that
the "pnfs" mount option not be specified.

PR:	254840
Comments by:	asomers
MFC after:	2 weeks
Differential Revision:	https://reviews.freebsd.org/D29475
This commit is contained in:
Rick Macklem 2021-04-11 14:34:57 -07:00
parent 70275a6735
commit 7763814fc9
9 changed files with 133 additions and 6 deletions

View File

@ -218,7 +218,7 @@ static struct nfsrv_lughash *nfsgroupnamehash;
static int nfs_bigreply[NFSV42_NPROCS] = { 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 1 };
1, 0, 0, 1, 0 };
/* local functions */
static int nfsrv_skipace(struct nfsrv_descript *nd, int *acesizep);
@ -301,6 +301,7 @@ static struct {
{ NFSV4OP_SETXATTR, 2, "Setxattr", 8, },
{ NFSV4OP_REMOVEXATTR, 2, "Rmxattr", 7, },
{ NFSV4OP_LISTXATTRS, 2, "Listxattr", 9, },
{ NFSV4OP_BINDCONNTOSESS, 1, "BindConSess", 11, },
};
/*
@ -309,7 +310,7 @@ static struct {
static int nfs_bigrequest[NFSV42_NPROCS] = {
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0
};
/*

View File

@ -561,6 +561,7 @@ int nfsrpc_listextattr(vnode_t, uint64_t *, struct uio *, size_t *, bool *,
struct nfsvattr *, int *, struct ucred *, NFSPROC_T *);
int nfsrpc_rmextattr(vnode_t, const char *, struct nfsvattr *, int *,
struct ucred *, NFSPROC_T *);
void nfsrpc_bindconnsess(CLIENT *, void *, struct ucred *);
/* nfs_clstate.c */
int nfscl_open(vnode_t, u_int8_t *, int, u_int32_t, int,

View File

@ -81,4 +81,9 @@ struct nfsv4node {
printf(__VA_ARGS__); \
} while (0)
struct nfscl_reconarg {
int minorvers;
uint8_t sessionid[NFSX_V4SESSIONID];
};
#endif /* _NFS_NFSCL_H */

View File

@ -412,10 +412,13 @@
#define NFSPROC_RMEXTATTR 63
#define NFSPROC_LISTEXTATTR 64
/* BindConnectionToSession, done by the krpc for a new connection. */
#define NFSPROC_BINDCONNTOSESS 65
/*
* Must be defined as one higher than the last NFSv4.2 Proc# above.
*/
#define NFSV42_NPROCS 65
#define NFSV42_NPROCS 66
#endif /* NFS_V3NPROCS */
@ -444,7 +447,7 @@ struct nfsstatsv1 {
uint64_t readlink_bios;
uint64_t biocache_readdirs;
uint64_t readdir_bios;
uint64_t rpccnt[NFSV42_NPROCS + 15];
uint64_t rpccnt[NFSV42_NPROCS + 14];
uint64_t rpcretries;
uint64_t srvrpccnt[NFSV42_NOPS + NFSV4OP_FAKENOPS + 15];
uint64_t reserved_0;
@ -509,7 +512,7 @@ struct nfsstatsov1 {
uint64_t readlink_bios;
uint64_t biocache_readdirs;
uint64_t readdir_bios;
uint64_t rpccnt[NFSV42_NPROCS + 4];
uint64_t rpccnt[NFSV42_NPROCS + 3];
uint64_t rpcretries;
uint64_t srvrpccnt[NFSV42_PURENOPS + NFSV4OP_FAKENOPS];
uint64_t reserved_0;

View File

@ -394,10 +394,13 @@
#define NFSPROC_RMEXTATTR 63
#define NFSPROC_LISTEXTATTR 64
/* BindConnectionToSession, done by the krpc for a new connection. */
#define NFSPROC_BINDCONNTOSESS 65
/*
* Must be defined as one higher than the last NFSv4.2 Proc# above.
*/
#define NFSV42_NPROCS 65
#define NFSV42_NPROCS 66
#endif /* NFS_V3NPROCS */

View File

@ -946,6 +946,8 @@ nfsrpc_setclient(struct nfsmount *nmp, struct nfsclclient *clp, int reclaim,
struct nfsclds *dsp, *odsp;
struct in6_addr a6;
struct nfsclsession *tsep;
struct rpc_reconupcall recon;
struct nfscl_reconarg *rcp;
if (nfsboottime.tv_sec == 0)
NFSSETBOOTTIME(nfsboottime);
@ -1019,6 +1021,23 @@ nfsrpc_setclient(struct nfsmount *nmp, struct nfsclclient *clp, int reclaim,
NFSCL_DEBUG(1, "aft createsess=%d\n", error);
}
if (error == 0) {
/*
* If the session supports a backchannel, set up
* the BindConnectionToSession call in the krpc
* so that it is done on a reconnection.
*/
if (nfscl_enablecallb != 0 && nfs_numnfscbd > 0) {
rcp = mem_alloc(sizeof(*rcp));
rcp->minorvers = nmp->nm_minorvers;
memcpy(rcp->sessionid,
dsp->nfsclds_sess.nfsess_sessionid,
NFSX_V4SESSIONID);
recon.call = nfsrpc_bindconnsess;
recon.arg = rcp;
CLNT_CONTROL(nmp->nm_client, CLSET_RECONUPCALL,
&recon);
}
NFSLOCKMNT(nmp);
/*
* The old sessions cannot be safely free'd
@ -8709,3 +8728,74 @@ nfsm_split(struct mbuf *mp, uint64_t xfer)
m->m_next = NULL;
return (m2);
}
/*
* Do the NFSv4.1 Bind Connection to Session.
* Called from the reconnect layer of the krpc (sys/rpc/clnt_rc.c).
*/
void
nfsrpc_bindconnsess(CLIENT *cl, void *arg, struct ucred *cr)
{
struct nfscl_reconarg *rcp = (struct nfscl_reconarg *)arg;
uint32_t res, *tl;
struct nfsrv_descript nfsd;
struct nfsrv_descript *nd = &nfsd;
struct rpc_callextra ext;
struct timeval utimeout;
enum clnt_stat stat;
int error;
nfscl_reqstart(nd, NFSPROC_BINDCONNTOSESS, NULL, NULL, 0, NULL, NULL,
NFS_VER4, rcp->minorvers);
NFSM_BUILD(tl, uint32_t *, NFSX_V4SESSIONID + 2 * NFSX_UNSIGNED);
memcpy(tl, rcp->sessionid, NFSX_V4SESSIONID);
tl += NFSX_V4SESSIONID / NFSX_UNSIGNED;
*tl++ = txdr_unsigned(NFSCDFC4_FORE_OR_BOTH);
*tl = newnfs_false;
memset(&ext, 0, sizeof(ext));
utimeout.tv_sec = 30;
utimeout.tv_usec = 0;
ext.rc_auth = authunix_create(cr);
nd->nd_mrep = NULL;
stat = CLNT_CALL_MBUF(cl, &ext, NFSV4PROC_COMPOUND, nd->nd_mreq,
&nd->nd_mrep, utimeout);
AUTH_DESTROY(ext.rc_auth);
if (stat != RPC_SUCCESS) {
printf("nfsrpc_bindconnsess: call failed stat=%d\n", stat);
return;
}
if (nd->nd_mrep == NULL) {
printf("nfsrpc_bindconnsess: no reply args\n");
return;
}
error = 0;
newnfs_realign(&nd->nd_mrep, M_WAITOK);
nd->nd_md = nd->nd_mrep;
nd->nd_dpos = mtod(nd->nd_md, char *);
NFSM_DISSECT(tl, uint32_t *, 2 * NFSX_UNSIGNED);
nd->nd_repstat = fxdr_unsigned(uint32_t, *tl++);
if (nd->nd_repstat == NFSERR_OK) {
res = fxdr_unsigned(uint32_t, *tl);
if (res > 0 && (error = nfsm_advance(nd, NFSM_RNDUP(res),
-1)) != 0)
goto nfsmout;
NFSM_DISSECT(tl, uint32_t *, NFSX_V4SESSIONID +
4 * NFSX_UNSIGNED);
tl += 3;
if (!NFSBCMP(tl, rcp->sessionid, NFSX_V4SESSIONID)) {
tl += NFSX_V4SESSIONID / NFSX_UNSIGNED;
res = fxdr_unsigned(uint32_t, *tl);
if (res != NFSCDFS4_BOTH)
printf("nfsrpc_bindconnsess: did not "
"return FS4_BOTH\n");
} else
printf("nfsrpc_bindconnsess: not same "
"sessionid\n");
} else if (nd->nd_repstat != NFSERR_BADSESSION)
printf("nfsrpc_bindconnsess: returned %d\n", nd->nd_repstat);
nfsmout:
if (error != 0)
printf("nfsrpc_bindconnsess: reply bad xdr\n");
m_freem(nd->nd_mrep);
}

View File

@ -360,6 +360,12 @@ enum clnt_stat clnt_call_private(CLIENT *, struct rpc_callextra *, rpcproc_t,
#define CLSET_TLS 30 /* set TLS for socket */
#define CLSET_BLOCKRCV 31 /* Temporarily block reception */
#define CLSET_TLSCERTNAME 32 /* TLS certificate file name */
/* Structure used as the argument for CLSET_RECONUPCALL. */
struct rpc_reconupcall {
void (*call)(CLIENT *, void *, struct ucred *);
void *arg;
};
#define CLSET_RECONUPCALL 33 /* Reconnect upcall */
#endif

View File

@ -111,6 +111,8 @@ clnt_reconnect_create(
rc->rc_client = NULL;
rc->rc_tls = false;
rc->rc_tlscertname = NULL;
rc->rc_reconcall = NULL;
rc->rc_reconarg = NULL;
cl->cl_refs = 1;
cl->cl_ops = &clnt_reconnect_ops;
@ -213,6 +215,9 @@ clnt_reconnect_connect(CLIENT *cl)
goto out;
}
}
if (newclient != NULL && rc->rc_reconcall != NULL)
(*rc->rc_reconcall)(newclient, rc->rc_reconarg,
rc->rc_ucred);
}
td->td_ucred = oldcred;
@ -408,6 +413,7 @@ clnt_reconnect_control(CLIENT *cl, u_int request, void *info)
struct rc_data *rc = (struct rc_data *)cl->cl_private;
SVCXPRT *xprt;
size_t slen;
struct rpc_reconupcall *upcp;
if (info == NULL) {
return (FALSE);
@ -513,6 +519,12 @@ clnt_reconnect_control(CLIENT *cl, u_int request, void *info)
strlcpy(rc->rc_tlscertname, info, slen);
break;
case CLSET_RECONUPCALL:
upcp = (struct rpc_reconupcall *)info;
rc->rc_reconcall = upcp->call;
rc->rc_reconarg = upcp->arg;
break;
default:
return (FALSE);
}
@ -555,12 +567,15 @@ clnt_reconnect_destroy(CLIENT *cl)
CLNT_DESTROY(rc->rc_client);
if (rc->rc_backchannel) {
xprt = (SVCXPRT *)rc->rc_backchannel;
KASSERT(xprt->xp_socket == NULL,
("clnt_reconnect_destroy: xp_socket not NULL"));
xprt_unregister(xprt);
SVC_RELEASE(xprt);
}
crfree(rc->rc_ucred);
mtx_destroy(&rc->rc_lock);
mem_free(rc->rc_tlscertname, 0); /* 0 ok, since arg. ignored. */
mem_free(rc->rc_reconarg, 0);
mem_free(rc, sizeof(*rc));
mem_free(cl, sizeof (CLIENT));
}

View File

@ -81,6 +81,9 @@ struct rc_data {
void *rc_backchannel;
bool rc_tls; /* Enable TLS on connection */
char *rc_tlscertname;
void (*rc_reconcall)(CLIENT *, void *,
struct ucred *); /* reconection upcall */
void *rc_reconarg; /* upcall arg */
};
/* Bits for ct_rcvstate. */