SUNRPC: report local rpcbind calls that get no answer

A caller that creates many listeners in one operation calls svc_register()
once for each of them. Every call waits for the local rpcbind on its own,
so a rpcbind that never answers costs the caller one timeout per listener.
The caller has no way to learn that the first call already failed.

Split rpcb_register_call() failures by whether rpcbind answered:

 - answered: a FALSE reply, or an RPC-level rejection that
   rpc_decode_header() derives from the reply. -EACCES (FALSE reply or
   AUTH_ERROR), -EPROTONOSUPPORT, -EPFNOSUPPORT, -EOPNOTSUPP.
 - never sent: -ENOMEM, -EMSGSIZE, -ERESTARTSYS.
 - no answer: everything else, i.e. transport errors.

Any no-answer error gets represented by -EIO, which is already what the
RPC layer reports for most of it: rpc_check_timeout() returns -EIO for a
soft timeout without RPC_TASK_TIMEOUT, and call_status() documents -EIO
as "shutdown or soft timeout".

Keep a count of the number of rpcbind failures in the serv. Later
patches will use that to watch for hard rpcbind failures, and alter
their behavior accordingly.

Assisted-by: LLM
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20260901-nfsd-nl-hang-v5-6-a540d86668b0@kernel.org
Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
Jeff Layton 2026-09-01 09:39:44 -04:00 committed by Chuck Lever
parent 8e590a1685
commit 202a557d0c
4 changed files with 67 additions and 7 deletions

View File

@ -199,7 +199,8 @@ struct rpc_xprt *rpc_task_get_xprt(struct rpc_clnt *clnt,
int rpcb_create_local(struct net *);
void rpcb_put_local(struct net *);
int rpcb_register(struct net *, u32, u32, int, unsigned short);
int rpcb_register(struct net *net, u32 prog, u32 vers, int prot,
unsigned short port);
int rpcb_v4_register(struct net *net, const u32 program,
const u32 version,
const struct sockaddr *address,

View File

@ -78,6 +78,7 @@ struct svc_serv {
unsigned int sv_max_payload; /* datagram payload size */
unsigned int sv_max_mesg; /* max_payload + 1 page for overheads */
unsigned int sv_xdrsize; /* XDR buffer size */
atomic_t sv_rpcb_failures; /* unanswered rpcbind calls */
struct list_head sv_permsocks; /* all permanent sockets */
struct list_head sv_tempsocks; /* all temporary sockets */
int sv_tmpcnt; /* count of temporary "valid" sockets */
@ -451,6 +452,7 @@ int sunrpc_set_pool_mode(const char *val);
int sunrpc_get_pool_mode(char *val, size_t size);
void svc_rpcb_cleanup(struct svc_serv *serv, struct net *net);
int svc_bind(struct svc_serv *serv, struct net *net);
unsigned int svc_rpcb_failure_count(struct svc_serv *serv);
struct svc_serv *svc_create(struct svc_program *, unsigned int,
int (*threadfn)(void *data));
bool svc_rqst_replace_page(struct svc_rqst *rqstp,
@ -471,8 +473,9 @@ unsigned int svc_serv_maxthreads(const struct svc_serv *serv);
int svc_pool_stats_open(struct svc_info *si, struct file *file);
void svc_process(struct svc_rqst *rqstp);
void svc_process_bc(struct rpc_rqst *req, struct svc_rqst *rqstp);
int svc_register(const struct svc_serv *, struct net *, const int,
const unsigned short, const unsigned short);
int svc_register(struct svc_serv *serv, struct net *net,
const int family, const unsigned short proto,
const unsigned short port);
void svc_wake_up(struct svc_serv *);
void svc_reserve(struct svc_rqst *rqstp, int space);

View File

@ -412,7 +412,8 @@ static struct rpc_clnt *rpcb_create(struct net *net, const char *nodename,
return rpc_create(&args);
}
static int rpcb_register_call(struct sunrpc_net *sn, struct rpc_clnt *clnt, struct rpc_message *msg, bool is_set)
static int rpcb_register_call(struct sunrpc_net *sn, struct rpc_clnt *clnt,
struct rpc_message *msg, bool is_set)
{
int flags = RPC_TASK_NOCONNECT;
int error, result = 0;
@ -422,8 +423,23 @@ static int rpcb_register_call(struct sunrpc_net *sn, struct rpc_clnt *clnt, stru
msg->rpc_resp = &result;
error = rpc_call_sync(clnt, msg, flags);
if (error < 0)
return error;
if (error < 0) {
switch (error) {
/* rpcbind answered; the reply itself carries the error */
case -EPROTONOSUPPORT:
case -EPFNOSUPPORT:
case -EOPNOTSUPP:
case -EACCES:
/* the call never made it onto the wire */
case -ENOMEM:
case -EMSGSIZE:
/* the caller is going away; this says nothing about rpcbind */
case -ERESTARTSYS:
return error;
}
/* anything else, we assume that rpcbind isn't functional */
return -EIO;
}
if (!result)
return -EACCES;

View File

@ -1179,10 +1179,40 @@ int svc_generic_rpcbind_set(struct net *net,
error = svc_rpcbind_set_version(net, progp, version,
family, proto, port);
/* -EIO means no answer, not a refusal, so vs_rpcb_optnl must keep it. */
if (error == -EIO)
return error;
return (vers->vs_rpcb_optnl) ? 0 : error;
}
EXPORT_SYMBOL_GPL(svc_generic_rpcbind_set);
/**
* svc_rpcb_failure_count - local rpcbind calls for @serv that got no answer
* @serv: RPC service to query
*
* svc_register() adds one for each of its calls that got no answer. A reply
* that refuses one entry does not count, because rpcbind answered and the
* next entry may still succeed.
*
* The count is kept per serv rather than per net. The local rpcbind client
* is per-net and lockd shares it, but a count that another service can move
* says nothing about this serv's own calls.
*
* This is for callers that cannot see the svc_register() return, because a
* transport class sits in between. Such a caller reads the count before it
* starts and compares as it goes, so there is no state to reset between
* operations. The count never resets, and callers must not attach meaning
* to the value itself.
*
* Return: the number of unanswered calls since this serv was created.
*/
unsigned int svc_rpcb_failure_count(struct svc_serv *serv)
{
return atomic_read(&serv->sv_rpcb_failures);
}
EXPORT_SYMBOL_GPL(svc_rpcb_failure_count);
/**
* svc_register - register an RPC service with the local portmapper
* @serv: svc_serv struct for the service to register
@ -1193,10 +1223,11 @@ EXPORT_SYMBOL_GPL(svc_generic_rpcbind_set);
*
* Service is registered for any address in the passed-in protocol family
*/
int svc_register(const struct svc_serv *serv, struct net *net,
int svc_register(struct svc_serv *serv, struct net *net,
const int family, const unsigned short proto,
const unsigned short port)
{
bool noanswer = false;
unsigned int p, i;
int error = 0;
@ -1208,10 +1239,16 @@ int svc_register(const struct svc_serv *serv, struct net *net,
struct svc_program *progp = &serv->sv_programs[p];
for (i = 0; i < progp->pg_nvers; i++) {
const struct svc_version *vers = progp->pg_vers[i];
int ret;
ret = progp->pg_rpcbind_set(net, progp, i,
family, proto, port);
if (ret == -EIO) {
noanswer = true;
if (vers && vers->vs_rpcb_optnl)
ret = 0;
}
if (ret < 0) {
printk(KERN_WARNING "svc: failed to register "
"%sv%u RPC service (errno %d).\n",
@ -1223,6 +1260,9 @@ int svc_register(const struct svc_serv *serv, struct net *net,
}
}
if (noanswer)
atomic_inc(&serv->sv_rpcb_failures);
return error;
}