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


Reply via email to