diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 6e57e4d20e75..f32311f2d7cf 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2087,6 +2087,128 @@ static int nfsd_nl_validate_listeners(struct genl_info *info) return count; } +static size_t nfsd_nl_listener_set_msgsize(struct svc_serv *serv) +{ + size_t size = GENL_HDRLEN + /* genlmsg_iput() */ + nla_total_size(0); /* userspace-rpcbind */ + struct svc_xprt *xprt; + unsigned int p; + + lockdep_assert_held(&nfsd_mutex); + + for (p = 0; p < serv->sv_nprogs; p++) + size += serv->sv_programs[p].pg_nvers * + (nla_total_size(0) + /* rpcbind nest */ + nla_total_size(sizeof(u32)) + /* program */ + nla_total_size(sizeof(u32)) + /* version */ + nla_total_size(sizeof(u32))); /* flags */ + + spin_lock_bh(&serv->sv_lock); + list_for_each_entry(xprt, &serv->sv_permsocks, xpt_list) { + if (!test_bit(XPT_RPCB_UNREG, &xprt->xpt_flags)) + continue; + size += nla_total_size(0) + /* addr nest */ + nla_total_size(strlen(xprt->xpt_class->xcl_name) + 1) + + nla_total_size(sizeof(struct sockaddr_storage)); + } + spin_unlock_bh(&serv->sv_lock); + + return size; +} + +static struct sk_buff * +nfsd_nl_listener_set_msg(struct genl_info *info, struct net *net, + struct svc_serv *serv) +{ + struct svc_xprt *xprt; + struct sk_buff *skb; + unsigned int p, i; + void *hdr; + int err; + + lockdep_assert_held(&nfsd_mutex); + + skb = genlmsg_new(nfsd_nl_listener_set_msgsize(serv), GFP_KERNEL); + if (!skb) + return ERR_PTR(-ENOMEM); + + hdr = genlmsg_iput(skb, info); + if (!hdr) { + err = -EMSGSIZE; + goto err_free_msg; + } + + if (nla_put_flag(skb, NFSD_A_SERVER_SOCK_USERSPACE_RPCBIND)) { + err = -EMSGSIZE; + goto err_free_msg; + } + + for (p = 0; p < serv->sv_nprogs; p++) { + const struct svc_program *progp = &serv->sv_programs[p]; + + for (i = 0; i < progp->pg_nvers; i++) { + struct nlattr *attr; + u32 flags = 0; + + if (!nfsd_version_registerable(net, progp, i)) + continue; + + if (progp->pg_vers[i]->vs_need_cong_ctrl) + flags |= NFSD_RPCBIND_FLAGS_NO_UDP; + + attr = nla_nest_start(skb, NFSD_A_SERVER_SOCK_RPCBIND); + if (!attr) { + err = -EMSGSIZE; + goto err_free_msg; + } + if (nla_put_u32(skb, NFSD_A_RPCBIND_PROGRAM, + progp->pg_prog) || + nla_put_u32(skb, NFSD_A_RPCBIND_VERSION, i) || + (flags && nla_put_u32(skb, NFSD_A_RPCBIND_FLAGS, + flags))) { + err = -EMSGSIZE; + goto err_free_msg; + } + nla_nest_end(skb, attr); + } + } + + spin_lock_bh(&serv->sv_lock); + list_for_each_entry(xprt, &serv->sv_permsocks, xpt_list) { + struct nlattr *attr; + + if (!test_bit(XPT_RPCB_UNREG, &xprt->xpt_flags)) + continue; + + attr = nla_nest_start(skb, NFSD_A_SERVER_SOCK_ADDR); + if (!attr) { + err = -EMSGSIZE; + goto err_serv_unlock; + } + + if (nla_put_string(skb, NFSD_A_SOCK_TRANSPORT_NAME, + xprt->xpt_class->xcl_name) || + nla_put(skb, NFSD_A_SOCK_ADDR, + sizeof(struct sockaddr_storage), + &xprt->xpt_local)) { + err = -EMSGSIZE; + goto err_serv_unlock; + } + + nla_nest_end(skb, attr); + } + spin_unlock_bh(&serv->sv_lock); + + genlmsg_end(skb, hdr); + return skb; + +err_serv_unlock: + spin_unlock_bh(&serv->sv_lock); +err_free_msg: + nlmsg_free(skb); + return ERR_PTR(err); +} + /** * nfsd_nl_listener_set_doit - set the nfs running sockets * @skb: reply buffer @@ -2100,6 +2222,7 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info) const struct nlattr *bad_attr = NULL; struct svc_xprt *xprt, *tmp; const char *bad_xprt = NULL; + struct sk_buff *rskb = NULL; unsigned int rpcb_failures; const struct nlattr *attr; bool skipped_rpcb = false; @@ -2289,12 +2412,35 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info) "rpcbind did not answer, some listeners are not registered"); } + /* + * Build the reply before the serv can go away, and only on success. + * A caller that got an errno has nothing to register. + */ + if (!err && userspace_rpcbind) { + rskb = nfsd_nl_listener_set_msg(info, net, serv); + if (IS_ERR(rskb)) { + err = PTR_ERR(rskb); + rskb = NULL; + /* + * The listeners are up and the errno alone reads as + * if nothing happened. Retrying is safe: a request + * that matches the running set recreates nothing. + */ + NL_SET_ERR_MSG(info->extack, + "listeners are up but the reply could not be built; retry to fetch it"); + } + } + if (!serv->sv_nrthreads && list_empty(&nn->nfsd_serv->sv_permsocks)) nfsd_destroy_serv(net); out_unlock_mtx: mutex_unlock(&nfsd_mutex); + /* rskb is only built once err is known to be zero. */ + if (rskb) + return genlmsg_reply(rskb, info); + return err; } diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index dcce45d58322..69e3e92b3ec1 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -117,6 +117,8 @@ extern const struct svc_version localio_version1; enum vers_op {NFSD_SET, NFSD_CLEAR, NFSD_TEST, NFSD_AVAIL }; int nfsd_vers(struct nfsd_net *nn, int vers, enum vers_op change); +bool nfsd_version_registerable(struct net *net, + const struct svc_program *progp, u32 version); int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change); void nfsd_reset_versions(struct nfsd_net *nn); int nfsd_create_serv(struct net *net, bool no_rpcbind); diff --git a/fs/nfsd/nfssvc.c b/fs/nfsd/nfssvc.c index ef520d0562d6..cbc989238710 100644 --- a/fs/nfsd/nfssvc.c +++ b/fs/nfsd/nfssvc.c @@ -41,11 +41,6 @@ atomic_t nfsd_th_cnt = ATOMIC_INIT(0); static int nfsd(void *vrqstp); #if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) -static int nfsd_acl_rpcbind_set(struct net *, - const struct svc_program *, - u32, int, - unsigned short, - unsigned short); static __be32 nfsd_acl_init_request(struct svc_rqst *, const struct svc_program *, struct svc_process_info *); @@ -127,7 +122,7 @@ struct svc_program nfsd_programs[] = { .pg_class = "nfsd", .pg_authenticate = svc_set_client, .pg_init_request = nfsd_acl_init_request, - .pg_rpcbind_set = nfsd_acl_rpcbind_set, + .pg_rpcbind_set = nfsd_rpcbind_set, }, #endif /* defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) */ #if IS_ENABLED(CONFIG_NFS_LOCALIO) @@ -813,18 +808,6 @@ nfsd_support_acl_version(int vers) return false; } -static int -nfsd_acl_rpcbind_set(struct net *net, const struct svc_program *progp, - u32 version, int family, unsigned short proto, - unsigned short port) -{ - if (!nfsd_support_acl_version(version) || - !nfsd_vers(net_generic(net, nfsd_net_id), version, NFSD_TEST)) - return 0; - return svc_generic_rpcbind_set(net, progp, version, family, - proto, port); -} - static __be32 nfsd_acl_init_request(struct svc_rqst *rqstp, const struct svc_program *progp, @@ -859,12 +842,43 @@ nfsd_acl_init_request(struct svc_rqst *rqstp, } #endif +/** + * nfsd_version_registerable - would nfsd register [@progp, @version]? + * @net: network namespace to query + * @progp: RPC program to query + * @version: RPC version to query + * + * Answers the question for a listener of any protocol. A caller that asks + * about one listener must apply vs_need_cong_ctrl itself. + * + * Return: true when the version is a candidate for rpcbind registration. + */ +bool nfsd_version_registerable(struct net *net, + const struct svc_program *progp, u32 version) +{ + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + + /* + * nfsd_acl_version[] is built by CONFIG_NFSD_V2_ACL and + * CONFIG_NFSD_V3_ACL, so a NULL entry already answers for nfsacl and + * nfsd_support_acl_version() would add nothing here. + */ + if (version >= progp->pg_nvers || !progp->pg_vers[version]) + return false; + + /* nfslocalio is hidden and never reaches rpcbind. */ + if (progp->pg_vers[version]->vs_hidden) + return false; + + return nfsd_vers(nn, version, NFSD_TEST); +} + static int nfsd_rpcbind_set(struct net *net, const struct svc_program *progp, u32 version, int family, unsigned short proto, unsigned short port) { - if (!nfsd_vers(net_generic(net, nfsd_net_id), version, NFSD_TEST)) + if (!nfsd_version_registerable(net, progp, version)) return 0; return svc_generic_rpcbind_set(net, progp, version, family, proto, port);