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 <dan@xxxxxxxxxxxx> --- 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