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.
An rpcbind failure can occur one of two ways: either rpcbind fails to respond, or it can respond with -EACCES to indicate that the user doesn't own the current record. Give the first case its own errno. rpcb_register_call() returns -ENAVAIL when the call got no answer, and the existing -EACCES continues to mean a FALSE reply. svc_generic_rpcbind_set() has to let -ENAVAIL past vs_rpcb_optnl, since it is not a refusal. svc_register() applies vs_rpcb_optnl to it instead, so a v4-only server still creates its listeners, and then keeps a running total in serv->sv_rpcb_failures. -ENAVAIL never escapes svc_register(). svc_rpcb_failure_count() reports the total. A caller reads the count before it starts and compares as it goes to determine if there have been errors. The users of this infrastructure will be added in later patches. Assisted-by: LLM Signed-off-by: Jeff Layton <[email protected]> --- include/linux/sunrpc/clnt.h | 3 ++- include/linux/sunrpc/svc.h | 7 +++++-- net/sunrpc/rpcb_clnt.c | 10 +++++++--- net/sunrpc/svc.c | 42 +++++++++++++++++++++++++++++++++++++++++- 4 files changed, 55 insertions(+), 7 deletions(-) diff --git a/include/linux/sunrpc/clnt.h b/include/linux/sunrpc/clnt.h index 3c2b8c355ab3..30344c0d6a9d 100644 --- a/include/linux/sunrpc/clnt.h +++ b/include/linux/sunrpc/clnt.h @@ -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, diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h index 2db1b9ec5658..5fa9417e034d 100644 --- a/include/linux/sunrpc/svc.h +++ b/include/linux/sunrpc/svc.h @@ -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); diff --git a/net/sunrpc/rpcb_clnt.c b/net/sunrpc/rpcb_clnt.c index 0aa376b82a52..c680137f0fca 100644 --- a/net/sunrpc/rpcb_clnt.c +++ b/net/sunrpc/rpcb_clnt.c @@ -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,10 @@ 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) + if (error == -EPROTONOSUPPORT) return error; + if (error < 0) + return -ENAVAIL; if (!result) return -EACCES; @@ -463,7 +466,8 @@ static int rpcb_register_call(struct sunrpc_net *sn, struct rpc_clnt *clnt, stru * IN6ADDR_ANY (ie available for all AF_INET and AF_INET6 * addresses). */ -int rpcb_register(struct net *net, u32 prog, u32 vers, int prot, unsigned short port) +int rpcb_register(struct net *net, u32 prog, u32 vers, int prot, + unsigned short port) { struct rpcbind_args map = { .r_prog = prog, diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index 4f402bbf97ba..54f8e8b0bf28 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -1179,10 +1179,40 @@ int svc_generic_rpcbind_set(struct net *net, error = svc_rpcbind_set_version(net, progp, version, family, proto, port); + /* -ENAVAIL is not a refusal, so vs_rpcb_optnl must not swallow it. */ + if (error == -ENAVAIL) + 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 == -ENAVAIL) { + noanswer = true; + ret = (vers && vers->vs_rpcb_optnl) ? + 0 : -ETIMEDOUT; + } 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; } -- 2.55.0

