2021-01-21 19:13:35

by Dan Aloni

[permalink] [raw]
Subject: [PATCH v1 3/5] nfs: Extend nconnect with remoteports and localports mount params

The new added mount parameters allow passing a vector of IP addresses to
be used with the extra transports that nconnect creates. The remoteports
parameter provides the destination addresses, and localports specifies
local address binds.

Signed-off-by: Dan Aloni <[email protected]>
---
fs/nfs/client.c | 24 ++++++
fs/nfs/fs_context.c | 171 ++++++++++++++++++++++++++++++++++++++
fs/nfs/internal.h | 4 +
include/linux/nfs_fs_sb.h | 2 +
4 files changed, 201 insertions(+)

diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index ff5c4d0d6d13..3560817ab5c4 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -166,6 +166,18 @@ struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init)

memcpy(&clp->cl_addr, cl_init->addr, cl_init->addrlen);
clp->cl_addrlen = cl_init->addrlen;
+ if (cl_init->localports) {
+ clp->cl_localports = vmalloc(sizeof(*cl_init->localports));
+ if (!clp->cl_localports)
+ goto error_cleanup;
+ *clp->cl_localports = *cl_init->localports;
+ }
+ if (cl_init->remoteports) {
+ clp->cl_remoteports = vmalloc(sizeof(*cl_init->remoteports));
+ if (!clp->cl_remoteports)
+ goto error_cleanup;
+ *clp->cl_remoteports = *cl_init->remoteports;
+ }

if (cl_init->hostname) {
err = -ENOMEM;
@@ -187,6 +199,10 @@ struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init)
return clp;

error_cleanup:
+ if (clp->cl_remoteports)
+ vfree(clp->cl_remoteports);
+ if (clp->cl_localports)
+ vfree(clp->cl_localports);
put_nfs_version(clp->cl_nfs_mod);
error_dealloc:
kfree(clp);
@@ -245,6 +261,10 @@ void nfs_free_client(struct nfs_client *clp)

put_net(clp->cl_net);
put_nfs_version(clp->cl_nfs_mod);
+ if (clp->cl_localports)
+ vfree(clp->cl_localports);
+ if (clp->cl_remoteports)
+ vfree(clp->cl_remoteports);
kfree(clp->cl_hostname);
kfree(clp->cl_acceptor);
kfree(clp);
@@ -508,6 +528,8 @@ int nfs_create_rpc_client(struct nfs_client *clp,
.nconnect = clp->cl_nconnect,
.address = (struct sockaddr *)&clp->cl_addr,
.addrsize = clp->cl_addrlen,
+ .localports = clp->cl_localports,
+ .remoteports = clp->cl_remoteports,
.timeout = cl_init->timeparms,
.servername = clp->cl_hostname,
.nodename = cl_init->nodename,
@@ -678,6 +700,8 @@ static int nfs_init_server(struct nfs_server *server,
.timeparms = &timeparms,
.cred = server->cred,
.nconnect = ctx->nfs_server.nconnect,
+ .localports = ctx->localports,
+ .remoteports = ctx->remoteports,
.init_flags = (1UL << NFS_CS_REUSEPORT),
};
struct nfs_client *clp;
diff --git a/fs/nfs/fs_context.c b/fs/nfs/fs_context.c
index 06894bcdea2d..3d41ba61b26d 100644
--- a/fs/nfs/fs_context.c
+++ b/fs/nfs/fs_context.c
@@ -49,6 +49,7 @@ enum nfs_param {
Opt_hard,
Opt_intr,
Opt_local_lock,
+ Opt_localports,
Opt_lock,
Opt_lookupcache,
Opt_migration,
@@ -65,6 +66,7 @@ enum nfs_param {
Opt_proto,
Opt_rdirplus,
Opt_rdma,
+ Opt_remoteports,
Opt_resvport,
Opt_retrans,
Opt_retry,
@@ -134,6 +136,7 @@ static const struct fs_parameter_spec nfs_fs_parameters[] = {
fs_param_neg_with_no|fs_param_deprecated, NULL),
fsparam_enum ("local_lock", Opt_local_lock, nfs_param_enums_local_lock),
fsparam_flag_no("lock", Opt_lock),
+ fsparam_string("localports", Opt_localports),
fsparam_enum ("lookupcache", Opt_lookupcache, nfs_param_enums_lookupcache),
fsparam_flag_no("migration", Opt_migration),
fsparam_u32 ("minorversion", Opt_minorversion),
@@ -150,6 +153,7 @@ static const struct fs_parameter_spec nfs_fs_parameters[] = {
fsparam_string("proto", Opt_proto),
fsparam_flag_no("rdirplus", Opt_rdirplus),
fsparam_flag ("rdma", Opt_rdma),
+ fsparam_string("remoteports", Opt_remoteports),
fsparam_flag_no("resvport", Opt_resvport),
fsparam_u32 ("retrans", Opt_retrans),
fsparam_string("retry", Opt_retry),
@@ -430,6 +434,146 @@ static int nfs_parse_version_string(struct fs_context *fc,
return 0;
}

+static int nfs_portgroup_add_parsed(struct fs_context *fc, struct rpc_portgroup *pg,
+ const char *type, struct sockaddr_storage *addr,
+ int len)
+{
+ if (pg->nr >= RPC_MAX_PORTS) {
+ nfs_invalf(fc, "NFS: portgroup for %s is too large, reached %d items",
+ type, pg->nr);
+ return -ENOSPC;
+ }
+
+ if (pg->nr > 0 && pg->addrs[0].ss_family != addr->ss_family) {
+ nfs_invalf(fc, "NFS: all portgroup addresses must be of the same family");
+ return -EINVAL;
+ }
+
+ pg->addrs[pg->nr] = *addr;
+ pg->nr++;
+
+ return 0;
+}
+
+/*
+ * Parse a single address and add to portgroup.
+ */
+static int nfs_portgroup_add_single(struct fs_context *fc, struct rpc_portgroup *pg,
+ const char *type, const char *single)
+{
+ struct sockaddr_storage addr;
+ size_t len = rpc_pton(fc->net_ns, single, strlen(single),
+ (struct sockaddr *)&addr, sizeof(addr));
+
+ if (len == 0) {
+ nfs_invalf(fc, "NFS: portgroup for %s, unable to parse address %s",
+ type, single);
+ return -EINVAL;
+ }
+
+ return nfs_portgroup_add_parsed(fc, pg, type, &addr, len);
+}
+
+/*
+ * Parse and add a portgroup address range. This is an inclusive address range
+ * that is delimited by '-', e.g. '192.168.0.1-192.168.0.16'.
+ */
+static int nfs_portgroup_add_range(struct fs_context *fc, struct rpc_portgroup *pg,
+ const char *type, const char *begin, const char *end)
+{
+ struct sockaddr_storage addr;
+ struct sockaddr_storage end_addr;
+ int ret;
+ size_t len = rpc_pton(fc->net_ns, begin, strlen(begin),
+ (struct sockaddr *)&addr, sizeof(addr)), end_len;
+
+ if (len == 0) {
+ nfs_invalf(fc, "NFS: portgroup for %s, unable to parse address %s",
+ type, begin);
+ return -EINVAL;
+ }
+
+ end_len = rpc_pton(fc->net_ns, end, strlen(end),
+ (struct sockaddr *)&end_addr, sizeof(end_addr));
+
+ if (end_len == 0) {
+ nfs_invalf(fc, "NFS: portgroup for %s, unable to parse address %s",
+ type, end);
+ return -EINVAL;
+ }
+
+ while (0 == (ret = nfs_portgroup_add_parsed(fc, pg, type, &addr, len))) {
+ /* Check if end of range reached */
+ if (rpc_cmp_addr((const struct sockaddr *)&addr,
+ (const struct sockaddr *)&end_addr))
+ break;
+
+ /* Bump address by one */
+ switch (addr.ss_family) {
+ case AF_INET: {
+ struct sockaddr_in *sin1 = (struct sockaddr_in *)&addr;
+ sin1->sin_addr.s_addr = htonl(ntohl(sin1->sin_addr.s_addr) + 1);
+ break;
+ }
+ case AF_INET6: {
+ nfs_invalf(fc, "NFS: IPv6 in address ranges not supported");
+ return -ENOTSUPP;
+ }
+ default:
+ nfs_invalf(fc, "NFS: address family %d not supported in ranges",
+ addr.ss_family);
+ return -ENOTSUPP;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * Parse and add a portgroup string. These are `~`-delimited single addresses
+ * or groups of addresses. An inclusive address range can be specified with '-'
+ * instead of specifying a single address.
+ */
+static int nfs_parse_portgroup(char *string, struct fs_context *fc,
+ struct rpc_portgroup **pg_out, const char *type)
+{
+ struct rpc_portgroup *pg = NULL;
+ char *string_scan = string, *item;
+ int ret;
+
+ if (!*pg_out) {
+ pg = vmalloc(sizeof(*pg));
+ if (!pg)
+ return -ENOMEM;
+
+ memset(pg, 0, sizeof(*pg));
+ *pg_out = pg;
+ } else {
+ pg = *pg_out;
+ }
+
+ while ((item = strsep(&string_scan, "~")) != NULL) {
+ const char *range_sep = strchr(item, '-');
+
+ if (range_sep != NULL) {
+ const char *range_start = strsep(&item, "-");
+ BUG_ON(range_start == NULL || item == NULL);
+ ret = nfs_portgroup_add_range(fc, pg, type,
+ range_start, item);
+ } else {
+ ret = nfs_portgroup_add_single(fc, pg, type, item);
+ }
+
+ if (ret)
+ return ret;
+ }
+
+ if (pg->nr == 0)
+ return nfs_invalf(fc, "NFS: passed empty portgroup is invalid");
+
+ return 0;
+}
+
/*
* Parse a single mount parameter.
*/
@@ -770,6 +914,24 @@ static int nfs_fs_context_parse_param(struct fs_context *fc,
goto out_invalid_value;
}
break;
+ case Opt_localports:
+ ret = nfs_parse_portgroup(param->string, fc, &ctx->localports, "local");
+
+ switch (ret) {
+ case -ENOMEM: goto out_nomem;
+ case -ENOSPC: goto out_portgroup_too_large;
+ case -EINVAL: goto out_invalid_address;
+ }
+ break;
+ case Opt_remoteports:
+ ret = nfs_parse_portgroup(param->string, fc, &ctx->remoteports, "remote");
+
+ switch (ret) {
+ case -ENOMEM: goto out_nomem;
+ case -ENOSPC: goto out_portgroup_too_large;
+ case -EINVAL: goto out_invalid_address;
+ }
+ break;

/*
* Special options
@@ -782,6 +944,9 @@ static int nfs_fs_context_parse_param(struct fs_context *fc,

return 0;

+out_nomem:
+ nfs_errorf(fc, "NFS: not enough memory to parse device name");
+ return -ENOMEM;
out_invalid_value:
return nfs_invalf(fc, "NFS: Bad mount option value specified");
out_invalid_address:
@@ -790,6 +955,8 @@ static int nfs_fs_context_parse_param(struct fs_context *fc,
return nfs_invalf(fc, "NFS: Value for '%s' out of range", param->key);
out_bad_transport:
return nfs_invalf(fc, "NFS: Unrecognized transport protocol");
+out_portgroup_too_large:
+ return -EINVAL;
}

/*
@@ -1394,6 +1561,10 @@ static void nfs_fs_context_free(struct fs_context *fc)
if (ctx->nfs_mod)
put_nfs_version(ctx->nfs_mod);
kfree(ctx->client_address);
+ if (ctx->localports)
+ vfree(ctx->localports);
+ if (ctx->remoteports)
+ vfree(ctx->remoteports);
kfree(ctx->mount_server.hostname);
kfree(ctx->nfs_server.export_path);
kfree(ctx->nfs_server.hostname);
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 62d3189745cd..8efdbd896b77 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -63,6 +63,8 @@ struct nfs_client_initdata {
const char *nodename; /* Hostname of the client */
const char *ip_addr; /* IP address of the client */
size_t addrlen;
+ struct rpc_portgroup *localports; /* Local addresses to bind */
+ struct rpc_portgroup *remoteports; /* Remote server addresses */
struct nfs_subversion *nfs_mod;
int proto;
u32 minorversion;
@@ -96,6 +98,8 @@ struct nfs_fs_context {
char *fscache_uniq;
unsigned short protofamily;
unsigned short mountfamily;
+ struct rpc_portgroup *localports;
+ struct rpc_portgroup *remoteports;

struct {
union {
diff --git a/include/linux/nfs_fs_sb.h b/include/linux/nfs_fs_sb.h
index 38e60ec742df..33fd23068546 100644
--- a/include/linux/nfs_fs_sb.h
+++ b/include/linux/nfs_fs_sb.h
@@ -50,6 +50,8 @@ struct nfs_client {
#define NFS_CS_REUSEPORT 8 /* - reuse src port on reconnect */
struct sockaddr_storage cl_addr; /* server identifier */
size_t cl_addrlen;
+ struct rpc_portgroup * cl_localports; /* Local addresses to bind */
+ struct rpc_portgroup * cl_remoteports; /* Remote server addresses */
char * cl_hostname; /* hostname of server */
char * cl_acceptor; /* GSSAPI acceptor name */
struct list_head cl_share_link; /* link in global client list */
--
2.26.2