kernel/0003-add_enfs_module_for_nfs_mount_option.patch
mingqian218472 67f9eb2535
Add feature for nfs client support multipath
Signed-off-by: mingqian218472 <zhangmingqian.zhang@huawei.com>
2023-11-13 00:29:34 +00:00

1209 lines
31 KiB
Diff

diff --git a/fs/nfs/enfs/Makefile b/fs/nfs/enfs/Makefile
new file mode 100644
index 000000000000..6e83eb23c668
--- /dev/null
+++ b/fs/nfs/enfs/Makefile
@@ -0,0 +1,18 @@
+obj-m += enfs.o
+
+#EXTRA_CFLAGS += -I$(PWD)/..
+
+enfs-y := enfs_init.o
+enfs-y += enfs_config.o
+enfs-y += mgmt_init.o
+enfs-y += enfs_multipath_client.o
+enfs-y += enfs_multipath_parse.o
+enfs-y += failover_path.o
+enfs-y += failover_time.o
+enfs-y += enfs_roundrobin.o
+enfs-y += enfs_multipath.o
+enfs-y += enfs_path.o
+enfs-y += enfs_proc.o
+enfs-y += enfs_remount.o
+enfs-y += pm_ping.o
+enfs-y += pm_state.o
diff --git a/fs/nfs/enfs/enfs.h b/fs/nfs/enfs/enfs.h
new file mode 100644
index 000000000000..be3d95220088
--- /dev/null
+++ b/fs/nfs/enfs/enfs.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Client-side ENFS multipath adapt header.
+ *
+ * Copyright (c) 2023. Huawei Technologies Co., Ltd. All rights reserved.
+ */
+
+#ifndef _ENFS_H_
+#define _ENFS_H_
+#include <linux/atomic.h>
+#include <linux/nfs.h>
+#include <linux/nfs4.h>
+#include <linux/nfs3.h>
+#include <linux/nfs_fs.h>
+#include <linux/nfs_fs_sb.h>
+#include "../enfs_adapter.h"
+
+#define IP_ADDRESS_LEN_MAX 64
+#define MAX_IP_PAIR_PER_MOUNT 8
+#define MAX_IP_INDEX (MAX_IP_PAIR_PER_MOUNT)
+#define MAX_SUPPORTED_LOCAL_IP_COUNT 8
+#define MAX_SUPPORTED_REMOTE_IP_COUNT 32
+#define MAX_DNS_NAME_LEN 512
+#define MAX_DNS_SUPPORTED 2
+#define EXTEND_CMD_MAX_BUF_LEN 65356
+
+
+struct nfs_ip_list {
+ int count;
+ struct sockaddr_storage address[MAX_SUPPORTED_REMOTE_IP_COUNT];
+ size_t addrlen[MAX_SUPPORTED_REMOTE_IP_COUNT];
+};
+
+struct NFS_ROUTE_DNS_S {
+ char dnsname[MAX_DNS_NAME_LEN]; // valid only if dnsExist is true
+};
+
+struct NFS_ROUTE_DNS_INFO_S {
+ int dnsNameCount; // Count of DNS name in the list
+ // valid only if dnsExist is true
+ struct NFS_ROUTE_DNS_S routeRemoteDnsList[MAX_DNS_SUPPORTED];
+};
+
+struct rpc_iostats;
+struct enfs_xprt_context {
+ struct sockaddr_storage srcaddr;
+ struct rpc_iostats *stats;
+ bool main;
+ atomic_t path_state;
+ atomic_t path_check_state;
+};
+
+static inline bool enfs_is_main_xprt(struct rpc_xprt *xprt)
+{
+ struct enfs_xprt_context *ctx = xprt->multipath_context;
+
+ if (!ctx)
+ return false;
+ return ctx->main;
+}
+
+#endif
diff --git a/fs/nfs/enfs/enfs_init.c b/fs/nfs/enfs/enfs_init.c
new file mode 100644
index 000000000000..4b55608191a7
--- /dev/null
+++ b/fs/nfs/enfs/enfs_init.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Client-side ENFS adapter.
+ *
+ * Copyright (c) 2023. Huawei Technologies Co., Ltd. All rights reserved.
+ */
+#include <linux/module.h>
+#include <linux/sunrpc/sched.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/nfs.h>
+#include <linux/nfs4.h>
+#include <linux/nfs3.h>
+#include <linux/nfs_fs.h>
+#include <linux/nfs_fs_sb.h>
+#include "enfs.h"
+#include "enfs_multipath_parse.h"
+#include "enfs_multipath_client.h"
+#include "enfs_remount.h"
+#include "init.h"
+#include "enfs_log.h"
+#include "enfs_multipath.h"
+#include "mgmt_init.h"
+
+struct enfs_adapter_ops enfs_adapter = {
+ .name = "enfs",
+ .owner = THIS_MODULE,
+ .parse_mount_options = nfs_multipath_parse_options,
+ .free_mount_options = nfs_multipath_free_options,
+ .client_info_init = nfs_multipath_client_info_init,
+ .client_info_free = nfs_multipath_client_info_free,
+ .client_info_match = nfs_multipath_client_info_match,
+ .client_info_show = nfs_multipath_client_info_show,
+ .remount_ip_list = enfs_remount_iplist,
+};
+
+int32_t enfs_init(void)
+{
+ int err;
+
+ err = enfs_multipath_init();
+ if (err) {
+ enfs_log_error("init multipath failed.\n");
+ goto out;
+ }
+
+ err = mgmt_init();
+ if (err != 0) {
+ enfs_log_error("init mgmt failed.\n");
+ goto out_tp_exit;
+ }
+
+ return 0;
+
+out_tp_exit:
+ enfs_multipath_exit();
+out:
+ return err;
+}
+
+void enfs_fini(void)
+{
+ mgmt_fini();
+
+ enfs_multipath_exit();
+}
+
+static int __init init_enfs(void)
+{
+ int ret;
+
+ ret = enfs_adapter_register(&enfs_adapter);
+ if (ret) {
+ pr_err("regist enfs_adapter fail. ret %d\n", ret);
+ return -1;
+ }
+
+ ret = enfs_init();
+ if (ret) {
+ enfs_adapter_unregister(&enfs_adapter);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void __exit exit_enfs(void)
+{
+ enfs_fini();
+ enfs_adapter_unregister(&enfs_adapter);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
+MODULE_DESCRIPTION("Nfs client router");
+MODULE_VERSION("1.0");
+
+module_init(init_enfs);
+module_exit(exit_enfs);
diff --git a/fs/nfs/enfs/enfs_multipath_client.c b/fs/nfs/enfs/enfs_multipath_client.c
new file mode 100644
index 000000000000..63c02898a42c
--- /dev/null
+++ b/fs/nfs/enfs/enfs_multipath_client.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Client-side ENFS adapter.
+ *
+ * Copyright (c) 2023. Huawei Technologies Co., Ltd. All rights reserved.
+ */
+#include <linux/types.h>
+#include <linux/nfs.h>
+#include <linux/nfs4.h>
+#include <linux/nfs_fs.h>
+#include <linux/nfs_fs_sb.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/addr.h>
+#include "enfs_multipath_client.h"
+#include "enfs_multipath_parse.h"
+
+int
+nfs_multipath_client_mount_info_init(struct multipath_client_info *client_info,
+ const struct nfs_client_initdata *client_init_data)
+{
+ struct multipath_mount_options *mount_options =
+ (struct multipath_mount_options *)client_init_data->enfs_option;
+
+ if (mount_options->local_ip_list) {
+ client_info->local_ip_list =
+ kzalloc(sizeof(struct nfs_ip_list), GFP_KERNEL);
+
+ if (!client_info->local_ip_list)
+ return -ENOMEM;
+
+ memcpy(client_info->local_ip_list, mount_options->local_ip_list,
+ sizeof(struct nfs_ip_list));
+ }
+
+ if (mount_options->remote_ip_list) {
+
+ client_info->remote_ip_list =
+ kzalloc(sizeof(struct nfs_ip_list), GFP_KERNEL);
+
+ if (!client_info->remote_ip_list) {
+ kfree(client_info->local_ip_list);
+ client_info->local_ip_list = NULL;
+ return -ENOMEM;
+ }
+ memcpy(client_info->remote_ip_list,
+ mount_options->remote_ip_list,
+ sizeof(struct nfs_ip_list));
+ }
+
+ if (mount_options->pRemoteDnsInfo) {
+ client_info->pRemoteDnsInfo =
+ kzalloc(sizeof(struct NFS_ROUTE_DNS_INFO_S), GFP_KERNEL);
+
+ if (!client_info->pRemoteDnsInfo) {
+ kfree(client_info->local_ip_list);
+ client_info->local_ip_list = NULL;
+ kfree(client_info->remote_ip_list);
+ client_info->remote_ip_list = NULL;
+ return -ENOMEM;
+ }
+ memcpy(client_info->pRemoteDnsInfo,
+ mount_options->pRemoteDnsInfo,
+ sizeof(struct NFS_ROUTE_DNS_INFO_S));
+ }
+ return 0;
+}
+
+void nfs_multipath_client_info_free_work(struct work_struct *work)
+{
+
+ struct multipath_client_info *clp_info;
+
+ if (work == NULL)
+ return;
+
+ clp_info = container_of(work, struct multipath_client_info, work);
+
+ if (clp_info->local_ip_list != NULL) {
+ kfree(clp_info->local_ip_list);
+ clp_info->local_ip_list = NULL;
+ }
+ if (clp_info->remote_ip_list != NULL) {
+ kfree(clp_info->remote_ip_list);
+ clp_info->remote_ip_list = NULL;
+ }
+ kfree(clp_info);
+}
+
+void nfs_multipath_client_info_free(void *data)
+{
+ struct multipath_client_info *clp_info =
+ (struct multipath_client_info *)data;
+
+ if (clp_info == NULL)
+ return;
+ pr_info("free client info %p.\n", clp_info);
+ INIT_WORK(&clp_info->work, nfs_multipath_client_info_free_work);
+ schedule_work(&clp_info->work);
+}
+
+int nfs_multipath_client_info_init(void **data,
+ const struct nfs_client_initdata *cl_init)
+{
+ int rc;
+ struct multipath_client_info *info;
+ struct multipath_client_info **enfs_info;
+ /* no multi path info, no need do multipath init */
+ if (cl_init->enfs_option == NULL)
+ return 0;
+ enfs_info = (struct multipath_client_info **)data;
+ if (enfs_info == NULL)
+ return -EINVAL;
+
+ if (*enfs_info == NULL)
+ *enfs_info = kzalloc(sizeof(struct multipath_client_info),
+ GFP_KERNEL);
+
+ if (*enfs_info == NULL)
+ return -ENOMEM;
+
+ info = (struct multipath_client_info *)*enfs_info;
+ pr_info("init client info %p.\n", info);
+ rc = nfs_multipath_client_mount_info_init(info, cl_init);
+ if (rc) {
+ nfs_multipath_client_info_free((void *)info);
+ return rc;
+ }
+ return rc;
+}
+
+bool nfs_multipath_ip_list_info_match(const struct nfs_ip_list *ip_list_src,
+ const struct nfs_ip_list *ip_list_dst)
+{
+ int i;
+ int j;
+ bool is_find;
+ /* if both are equal or NULL, then return true. */
+ if (ip_list_src == ip_list_dst)
+ return true;
+
+ if ((ip_list_src == NULL || ip_list_dst == NULL))
+ return false;
+
+ if (ip_list_src->count != ip_list_dst->count)
+ return false;
+
+ for (i = 0; i < ip_list_src->count; i++) {
+ is_find = false;
+ for (j = 0; j < ip_list_src->count; j++) {
+ if (rpc_cmp_addr_port(
+ (const struct sockaddr *)
+ &ip_list_src->address[i],
+ (const struct sockaddr *)
+ &ip_list_dst->address[j])
+ ) {
+ is_find = true;
+ break;
+ }
+ }
+ if (is_find == false)
+ return false;
+ }
+ return true;
+}
+
+int
+nfs_multipath_dns_list_info_match(
+ const struct NFS_ROUTE_DNS_INFO_S *pRemoteDnsInfoSrc,
+ const struct NFS_ROUTE_DNS_INFO_S *pRemoteDnsInfoDst)
+{
+ int i;
+
+ /* if both are equal or NULL, then return true. */
+ if (pRemoteDnsInfoSrc == pRemoteDnsInfoDst)
+ return true;
+
+ if ((pRemoteDnsInfoSrc == NULL || pRemoteDnsInfoDst == NULL))
+ return false;
+
+ if (pRemoteDnsInfoSrc->dnsNameCount != pRemoteDnsInfoDst->dnsNameCount)
+ return false;
+
+ for (i = 0; i < pRemoteDnsInfoSrc->dnsNameCount; i++) {
+ if (!strcmp(pRemoteDnsInfoSrc->routeRemoteDnsList[i].dnsname,
+ pRemoteDnsInfoDst->routeRemoteDnsList[i].dnsname))
+ return false;
+ }
+ return true;
+}
+
+int nfs_multipath_client_info_match(void *src, void *dst)
+{
+ int ret = true;
+
+ struct multipath_client_info *src_info;
+ struct multipath_mount_options *dst_info;
+
+ src_info = (struct multipath_client_info *)src;
+ dst_info = (struct multipath_mount_options *)dst;
+ pr_info("try match client .\n");
+ ret = nfs_multipath_ip_list_info_match(src_info->local_ip_list,
+ dst_info->local_ip_list);
+ if (ret == false) {
+ pr_err("local_ip not match.\n");
+ return ret;
+ }
+
+ ret = nfs_multipath_ip_list_info_match(src_info->remote_ip_list,
+ dst_info->remote_ip_list);
+ if (ret == false) {
+ pr_err("remote_ip not match.\n");
+ return ret;
+ }
+
+ ret = nfs_multipath_dns_list_info_match(src_info->pRemoteDnsInfo,
+ dst_info->pRemoteDnsInfo);
+ if (ret == false) {
+ pr_err("dns not match.\n");
+ return ret;
+ }
+ pr_info("try match client ret %d.\n", ret);
+ return ret;
+}
+
+void nfs_multipath_print_ip_info(struct seq_file *mount_option,
+ struct nfs_ip_list *ip_list,
+ const char *type)
+{
+ char buf[IP_ADDRESS_LEN_MAX + 1];
+ int len = 0;
+ int i = 0;
+
+ seq_printf(mount_option, ",%s=", type);
+ for (i = 0; i < ip_list->count; i++) {
+ len = rpc_ntop((struct sockaddr *)&ip_list->address[i],
+ buf, IP_ADDRESS_LEN_MAX);
+ if (len > 0 && len < IP_ADDRESS_LEN_MAX)
+ buf[len] = '\0';
+
+ if (i == 0)
+ seq_printf(mount_option, "%s", buf);
+ else
+ seq_printf(mount_option, "~%s", buf);
+ dfprintk(MOUNT,
+ "NFS: show nfs mount option type:%s %s [%s]\n",
+ type, buf, __func__);
+ }
+}
+
+void nfs_multipath_print_dns_info(struct seq_file *mount_option,
+ struct NFS_ROUTE_DNS_INFO_S *pRemoteDnsInfo,
+ const char *type)
+{
+ int i = 0;
+
+ seq_printf(mount_option, ",%s=", type);
+ for (i = 0; i < pRemoteDnsInfo->dnsNameCount; i++) {
+ if (i == 0)
+ seq_printf(mount_option,
+ "[%s", pRemoteDnsInfo->routeRemoteDnsList[i].dnsname);
+ else if (i == pRemoteDnsInfo->dnsNameCount - 1)
+ seq_printf(mount_option, ",%s]",
+ pRemoteDnsInfo->routeRemoteDnsList[i].dnsname);
+ else
+ seq_printf(mount_option,
+ ",%s", pRemoteDnsInfo->routeRemoteDnsList[i].dnsname);
+ }
+}
+
+
+static void multipath_print_sockaddr(struct seq_file *seq,
+ struct sockaddr *addr)
+{
+ switch (addr->sa_family) {
+ case AF_INET: {
+ struct sockaddr_in *sin = (struct sockaddr_in *)addr;
+
+ seq_printf(seq, "%pI4", &sin->sin_addr);
+ return;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
+
+ seq_printf(seq, "%pI6", &sin6->sin6_addr);
+ return;
+ }
+ default:
+ break;
+ }
+ pr_err("unsupport family:%d\n", addr->sa_family);
+}
+
+static void multipath_print_enfs_info(struct seq_file *seq,
+ struct nfs_server *server)
+{
+ struct sockaddr_storage peeraddr;
+ struct rpc_clnt *next = server->client;
+
+ rpc_peeraddr(server->client,
+ (struct sockaddr *)&peeraddr, sizeof(peeraddr));
+ seq_puts(seq, ",enfs_info=");
+ multipath_print_sockaddr(seq, (struct sockaddr *)&peeraddr);
+
+ while (next->cl_parent) {
+ if (next == next->cl_parent)
+ break;
+ next = next->cl_parent;
+ }
+ seq_printf(seq, "_%u", next->cl_clid);
+}
+
+void nfs_multipath_client_info_show(struct seq_file *mount_option, void *data)
+{
+ struct nfs_server *server = data;
+ struct multipath_client_info *client_info =
+ server->nfs_client->cl_multipath_data;
+
+ dfprintk(MOUNT, "NFS: show nfs mount option[%s]\n", __func__);
+ if ((client_info->remote_ip_list) &&
+ (client_info->remote_ip_list->count > 0))
+ nfs_multipath_print_ip_info(mount_option,
+ client_info->remote_ip_list,
+ "remoteaddrs");
+
+ if ((client_info->local_ip_list) &&
+ (client_info->local_ip_list->count > 0))
+ nfs_multipath_print_ip_info(mount_option,
+ client_info->local_ip_list,
+ "localaddrs");
+
+ if ((client_info->pRemoteDnsInfo) &&
+ (client_info->pRemoteDnsInfo->dnsNameCount > 0))
+ nfs_multipath_print_dns_info(mount_option,
+ client_info->pRemoteDnsInfo,
+ "remotednsname");
+
+ multipath_print_enfs_info(mount_option, server);
+}
diff --git a/fs/nfs/enfs/enfs_multipath_client.h b/fs/nfs/enfs/enfs_multipath_client.h
new file mode 100644
index 000000000000..208f7260690d
--- /dev/null
+++ b/fs/nfs/enfs/enfs_multipath_client.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Client-side ENFS adapter.
+ *
+ * Copyright (c) 2023. Huawei Technologies Co., Ltd. All rights reserved.
+ */
+#ifndef _ENFS_MULTIPATH_CLIENT_H_
+#define _ENFS_MULTIPATH_CLIENT_H_
+
+#include "enfs.h"
+
+struct multipath_client_info {
+ struct work_struct work;
+ struct nfs_ip_list *remote_ip_list;
+ struct nfs_ip_list *local_ip_list;
+ struct NFS_ROUTE_DNS_INFO_S *pRemoteDnsInfo;
+ s64 client_id;
+};
+
+int nfs_multipath_client_info_init(void **data,
+ const struct nfs_client_initdata *cl_init);
+void nfs_multipath_client_info_free(void *data);
+int nfs_multipath_client_info_match(void *src, void *dst);
+void nfs_multipath_client_info_show(struct seq_file *mount_option, void *data);
+
+#endif
diff --git a/fs/nfs/enfs/enfs_multipath_parse.c b/fs/nfs/enfs/enfs_multipath_parse.c
new file mode 100644
index 000000000000..868782b0a628
--- /dev/null
+++ b/fs/nfs/enfs/enfs_multipath_parse.c
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Client-side ENFS adapter.
+ *
+ * Copyright (c) 2023. Huawei Technologies Co., Ltd. All rights reserved.
+ */
+#include <linux/types.h>
+#include <linux/nfs.h>
+#include <linux/nfs4.h>
+#include <linux/nfs_fs.h>
+#include <linux/nfs_fs_sb.h>
+#include <linux/kern_levels.h>
+#include <linux/sunrpc/addr.h>
+#include "enfs_multipath_parse.h"
+#include "enfs_log.h"
+
+#define NFSDBG_FACILITY NFSDBG_CLIENT
+
+void nfs_multipath_parse_ip_ipv6_add(struct sockaddr_in6 *sin6, int add_num)
+{
+ int i;
+
+ pr_info("NFS: before %08x%08x%08x%08x add_num: %d[%s]\n",
+ ntohl(sin6->sin6_addr.in6_u.u6_addr32[0]),
+ ntohl(sin6->sin6_addr.in6_u.u6_addr32[1]),
+ ntohl(sin6->sin6_addr.in6_u.u6_addr32[2]),
+ ntohl(sin6->sin6_addr.in6_u.u6_addr32[3]),
+ add_num, __func__);
+ for (i = 0; i < add_num; i++) {
+ sin6->sin6_addr.in6_u.u6_addr32[3] =
+ htonl(ntohl(sin6->sin6_addr.in6_u.u6_addr32[3]) + 1);
+
+ if (sin6->sin6_addr.in6_u.u6_addr32[3] != 0)
+ continue;
+
+ sin6->sin6_addr.in6_u.u6_addr32[2] =
+ htonl(ntohl(sin6->sin6_addr.in6_u.u6_addr32[2]) + 1);
+
+ if (sin6->sin6_addr.in6_u.u6_addr32[2] != 0)
+ continue;
+
+ sin6->sin6_addr.in6_u.u6_addr32[1] =
+ htonl(ntohl(sin6->sin6_addr.in6_u.u6_addr32[1]) + 1);
+
+ if (sin6->sin6_addr.in6_u.u6_addr32[1] != 0)
+ continue;
+
+ sin6->sin6_addr.in6_u.u6_addr32[0] =
+ htonl(ntohl(sin6->sin6_addr.in6_u.u6_addr32[0]) + 1);
+
+ if (sin6->sin6_addr.in6_u.u6_addr32[0] != 0)
+ continue;
+ }
+
+ return;
+
+}
+
+static int nfs_multipath_parse_ip_range(struct net *net_ns, const char *cursor,
+ struct nfs_ip_list *ip_list, enum nfs_multi_path_options type)
+{
+ struct sockaddr_storage addr;
+ struct sockaddr_storage tmp_addr;
+ int i;
+ size_t len;
+ int add_num = 1;
+ bool duplicate_flag = false;
+ bool is_complete = false;
+ struct sockaddr_in *sin4;
+ struct sockaddr_in6 *sin6;
+
+ pr_info("NFS: parsing nfs mount option '%s' type: %d[%s]\n",
+ cursor, type, __func__);
+ len = rpc_pton(net_ns, cursor, strlen(cursor),
+ (struct sockaddr *)&addr, sizeof(addr));
+ if (!len)
+ return -EINVAL;
+
+ if (addr.ss_family != ip_list->address[ip_list->count - 1].ss_family) {
+ pr_info("NFS: %s parsing nfs mount option type: %d fail.\n",
+ __func__, type);
+ return -EINVAL;
+ }
+
+ if (rpc_cmp_addr((const struct sockaddr *)
+ &ip_list->address[ip_list->count - 1],
+ (const struct sockaddr *)&addr)) {
+
+ pr_info("range ip is same ip.\n");
+ return 0;
+
+ }
+
+ while (true) {
+
+ tmp_addr = ip_list->address[ip_list->count - 1];
+
+ switch (addr.ss_family) {
+ case AF_INET:
+ sin4 = (struct sockaddr_in *)&tmp_addr;
+
+ sin4->sin_addr.s_addr =
+ htonl(ntohl(sin4->sin_addr.s_addr) + add_num);
+
+ pr_info("NFS: mount option ip%08x type: %d ipcont %d [%s]\n",
+ ntohl(sin4->sin_addr.s_addr),
+ type, ip_list->count, __func__);
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)&tmp_addr;
+ nfs_multipath_parse_ip_ipv6_add(sin6, add_num);
+ pr_info("NFS: mount option ip %08x%08x%08x%08x type: %d ipcont %d [%s]\n",
+ ntohl(sin6->sin6_addr.in6_u.u6_addr32[0]),
+ ntohl(sin6->sin6_addr.in6_u.u6_addr32[1]),
+ ntohl(sin6->sin6_addr.in6_u.u6_addr32[2]),
+ ntohl(sin6->sin6_addr.in6_u.u6_addr32[3]),
+ type, ip_list->count, __func__);
+ break;
+ // return -EOPNOTSUPP;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (rpc_cmp_addr((const struct sockaddr *)&tmp_addr,
+ (const struct sockaddr *)&addr)) {
+ is_complete = true;
+ }
+ // delete duplicate ip, continuosly repeat, skip it
+ for (i = 0; i < ip_list->count; i++) {
+ duplicate_flag = false;
+ if (rpc_cmp_addr((const struct sockaddr *)
+ &ip_list->address[i],
+ (const struct sockaddr *)&tmp_addr)) {
+ add_num++;
+ duplicate_flag = true;
+ break;
+ }
+ }
+
+ if (duplicate_flag == false) {
+ pr_info("this ip not duplicate;");
+ add_num = 1;
+ // if not repeat but omit limit return false
+ if ((type == LOCALADDR &&
+ ip_list->count >= MAX_SUPPORTED_LOCAL_IP_COUNT) ||
+ (type == REMOTEADDR &&
+ ip_list->count >= MAX_SUPPORTED_REMOTE_IP_COUNT)) {
+
+ pr_info("[MULTIPATH:%s] iplist for type %d reached %d, more than supported limit %d\n",
+ __func__, type, ip_list->count,
+ type == LOCALADDR ?
+ MAX_SUPPORTED_LOCAL_IP_COUNT :
+ MAX_SUPPORTED_REMOTE_IP_COUNT);
+ ip_list->count = 0;
+ return -ENOSPC;
+ }
+ ip_list->address[ip_list->count] = tmp_addr;
+
+ ip_list->addrlen[ip_list->count] =
+ ip_list->addrlen[ip_list->count - 1];
+
+ ip_list->count += 1;
+ }
+ if (is_complete == true)
+ break;
+ }
+ return 0;
+}
+
+int nfs_multipath_parse_ip_list_inter(struct nfs_ip_list *ip_list,
+ struct net *net_ns,
+ char *cursor, enum nfs_multi_path_options type)
+{
+ int i = 0;
+ struct sockaddr_storage addr;
+ struct sockaddr_storage swap;
+ int len;
+
+ pr_info("NFS: parsing nfs mount option '%s' type: %d[%s]\n",
+ cursor, type, __func__);
+
+ len = rpc_pton(net_ns, cursor,
+ strlen(cursor),
+ (struct sockaddr *)&addr, sizeof(addr));
+ if (!len)
+ return -EINVAL;
+
+ // check repeated ip
+ for (i = 0; i < ip_list->count; i++) {
+ if (rpc_cmp_addr((const struct sockaddr *)
+ &ip_list->address[i],
+ (const struct sockaddr *)&addr)) {
+
+ pr_info("NFS: mount option '%s' type:%d index %d same as before index %d [%s]\n",
+ cursor, type, ip_list->count, i, __func__);
+ // prevent this ip is beginning
+ // if repeated take it to the end of list
+ swap = ip_list->address[i];
+
+ ip_list->address[i] =
+ ip_list->address[ip_list->count-1];
+
+ ip_list->address[ip_list->count-1] = swap;
+ return 0;
+ }
+ }
+ // if not repeated, check exceed limit
+ if ((type == LOCALADDR &&
+ ip_list->count >= MAX_SUPPORTED_LOCAL_IP_COUNT) ||
+ (type == REMOTEADDR &&
+ ip_list->count >= MAX_SUPPORTED_REMOTE_IP_COUNT)) {
+
+ pr_info("[MULTIPATH:%s] iplist for type %d reached %d, more than supported limit %d\n",
+ __func__, type, ip_list->count,
+ type == LOCALADDR ?
+ MAX_SUPPORTED_LOCAL_IP_COUNT :
+ MAX_SUPPORTED_REMOTE_IP_COUNT);
+
+ ip_list->count = 0;
+ return -ENOSPC;
+ }
+ ip_list->address[ip_list->count] = addr;
+ ip_list->addrlen[ip_list->count] = len;
+ ip_list->count++;
+
+ return 0;
+}
+
+char *nfs_multipath_parse_ip_list_get_cursor(char **buf_to_parse, bool *single)
+{
+ char *cursor = NULL;
+ const char *single_sep = strchr(*buf_to_parse, '~');
+ const char *range_sep = strchr(*buf_to_parse, '-');
+
+ *single = true;
+ if (range_sep) {
+ if (range_sep > single_sep) { // A-B or A~B-C
+ if (single_sep == NULL) { // A-B
+ cursor = strsep(buf_to_parse, "-");
+ if (cursor)
+ *single = false;
+ } else// A~B-C
+ cursor = strsep(buf_to_parse, "~");
+ } else { // A-B~C
+ cursor = strsep(buf_to_parse, "-");
+ if (cursor)
+ *single = false;
+ }
+ } else { // A~B~C
+ cursor = strsep(buf_to_parse, "~");
+ }
+ return cursor;
+}
+
+bool nfs_multipath_parse_param_check(enum nfs_multi_path_options type,
+ struct multipath_mount_options *options)
+{
+ if (type == REMOUNTREMOTEADDR && options->remote_ip_list->count != 0) {
+ memset(options->remote_ip_list, 0, sizeof(struct nfs_ip_list));
+ return true;
+ }
+ if (type == REMOUNTLOCALADDR && options->local_ip_list->count != 0) {
+ memset(options->local_ip_list, 0, sizeof(struct nfs_ip_list));
+ return true;
+ }
+ if ((type == REMOTEADDR || type == REMOTEDNSNAME) &&
+ options->pRemoteDnsInfo->dnsNameCount != 0) {
+
+ pr_info("[MULTIPATH:%s] parse for %d ,already have dns\n",
+ __func__, type);
+ return false;
+ } else if ((type == REMOTEADDR || type == REMOTEDNSNAME) &&
+ options->remote_ip_list->count != 0) {
+
+ pr_info("[MULTIPATH:%s] parse for %d ,already have iplist\n",
+ __func__, type);
+ return false;
+ }
+ return true;
+}
+
+int nfs_multipath_parse_ip_list(char *buffer, struct net *net_ns,
+ struct multipath_mount_options *options,
+ enum nfs_multi_path_options type)
+{
+ char *buf_to_parse = NULL;
+ bool prev_range = false;
+ int ret = 0;
+ char *cursor = NULL;
+ bool single = true;
+ struct nfs_ip_list *ip_list_tmp = NULL;
+
+ if (!nfs_multipath_parse_param_check(type, options))
+ return -ENOTSUPP;
+
+ if (type == REMOUNTREMOTEADDR)
+ type = REMOTEADDR;
+
+ if (type == REMOUNTLOCALADDR)
+ type = LOCALADDR;
+
+ if (type == LOCALADDR)
+ ip_list_tmp = options->local_ip_list;
+ else
+ ip_list_tmp = options->remote_ip_list;
+
+ pr_info("NFS: parsing nfs mount option '%s' type: %d[%s]\n",
+ buffer, type, __func__);
+
+ buf_to_parse = buffer;
+ while (buf_to_parse != NULL) {
+ cursor =
+ nfs_multipath_parse_ip_list_get_cursor(&buf_to_parse, &single);
+ if (!cursor)
+ break;
+
+ if (single == false && prev_range == true) {
+ pr_info("NFS: mount option type: %d fail. Multiple Range.[%s]\n",
+ type, __func__);
+
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (prev_range == false) {
+ ret = nfs_multipath_parse_ip_list_inter(ip_list_tmp,
+ net_ns, cursor, type);
+ if (ret)
+ goto out;
+ if (single == false)
+ prev_range = true;
+ } else {
+ ret = nfs_multipath_parse_ip_range(net_ns, cursor,
+ ip_list_tmp, type);
+ if (ret != 0)
+ goto out;
+ prev_range = false;
+ }
+ }
+
+out:
+ if (ret)
+ memset(ip_list_tmp, 0, sizeof(struct nfs_ip_list));
+
+ return ret;
+}
+
+int nfs_multipath_parse_dns_list(char *buffer, struct net *net_ns,
+ struct multipath_mount_options *options)
+{
+ struct NFS_ROUTE_DNS_INFO_S *dns_name_list_tmp = NULL;
+ char *cursor = NULL;
+ char *bufToParse;
+
+ if (!nfs_multipath_parse_param_check(REMOTEDNSNAME, options))
+ return -ENOTSUPP;
+
+ pr_info("[MULTIPATH:%s] buffer %s\n", __func__, buffer);
+ // freed in nfs_free_parsed_mount_data
+ dns_name_list_tmp = kmalloc(sizeof(struct NFS_ROUTE_DNS_INFO_S),
+ GFP_KERNEL);
+ if (!dns_name_list_tmp)
+ return -ENOMEM;
+
+ dns_name_list_tmp->dnsNameCount = 0;
+ bufToParse = buffer;
+ while (bufToParse) {
+ if (dns_name_list_tmp->dnsNameCount >= MAX_DNS_SUPPORTED) {
+ pr_err("%s: dnsname for %s reached %d,more than supported limit %d\n",
+ __func__, cursor,
+ dns_name_list_tmp->dnsNameCount,
+ MAX_DNS_SUPPORTED);
+ dns_name_list_tmp->dnsNameCount = 0;
+ return -ENOSPC;
+ }
+ cursor = strsep(&bufToParse, "~");
+ if (!cursor)
+ break;
+
+ strcpy(dns_name_list_tmp->routeRemoteDnsList
+ [dns_name_list_tmp->dnsNameCount].dnsname,
+ cursor);
+ dns_name_list_tmp->dnsNameCount++;
+ }
+ if (dns_name_list_tmp->dnsNameCount == 0)
+ return -EINVAL;
+ options->pRemoteDnsInfo = dns_name_list_tmp;
+ return 0;
+}
+
+int nfs_multipath_parse_options_check_ipv4_valid(struct sockaddr_in *addr)
+{
+ if (addr->sin_addr.s_addr == 0 || addr->sin_addr.s_addr == 0xffffffff)
+ return -EINVAL;
+ return 0;
+}
+
+int nfs_multipath_parse_options_check_ipv6_valid(struct sockaddr_in6 *addr)
+{
+ if (addr->sin6_addr.in6_u.u6_addr32[0] == 0 &&
+ addr->sin6_addr.in6_u.u6_addr32[1] == 0 &&
+ addr->sin6_addr.in6_u.u6_addr32[2] == 0 &&
+ addr->sin6_addr.in6_u.u6_addr32[3] == 0)
+ return -EINVAL;
+
+ if (addr->sin6_addr.in6_u.u6_addr32[0] == 0xffffffff &&
+ addr->sin6_addr.in6_u.u6_addr32[1] == 0xffffffff &&
+ addr->sin6_addr.in6_u.u6_addr32[2] == 0xffffffff &&
+ addr->sin6_addr.in6_u.u6_addr32[3] == 0xffffffff)
+ return -EINVAL;
+ return 0;
+}
+
+int nfs_multipath_parse_options_check_ip_valid(struct sockaddr_storage *address)
+{
+ int rc = 0;
+
+ if (address->ss_family == AF_INET)
+ rc = nfs_multipath_parse_options_check_ipv4_valid(
+ (struct sockaddr_in *)address);
+ else if (address->ss_family == AF_INET6)
+ rc = nfs_multipath_parse_options_check_ipv6_valid(
+ (struct sockaddr_in6 *)address);
+ else
+ rc = -EINVAL;
+
+ return rc;
+}
+
+int nfs_multipath_parse_options_check_valid(
+ struct multipath_mount_options *options)
+{
+ int rc;
+ int i;
+
+ if (options == NULL)
+ return 0;
+
+ for (i = 0; i < options->local_ip_list->count; i++) {
+ rc = nfs_multipath_parse_options_check_ip_valid(
+ &options->local_ip_list->address[i]);
+ if (rc != 0)
+ return rc;
+ }
+
+ for (i = 0; i < options->remote_ip_list->count; i++) {
+ rc = nfs_multipath_parse_options_check_ip_valid(
+ &options->remote_ip_list->address[i]);
+ if (rc != 0)
+ return rc;
+ }
+
+ return 0;
+}
+int nfs_multipath_parse_options_check_duplicate(
+ struct multipath_mount_options *options)
+{
+ int i;
+ int j;
+
+ if (options == NULL ||
+ options->local_ip_list->count == 0 ||
+ options->remote_ip_list->count == 0)
+
+ return 0;
+
+ for (i = 0; i < options->local_ip_list->count; i++) {
+ for (j = 0; j < options->remote_ip_list->count; j++) {
+ if (rpc_cmp_addr((const struct sockaddr *)
+ &options->local_ip_list->address[i],
+ (const struct sockaddr *)
+ &options->remote_ip_list->address[j]))
+ return -ENOTSUPP;
+ }
+ }
+ return 0;
+}
+
+int nfs_multipath_parse_options_check(struct multipath_mount_options *options)
+{
+ int rc = 0;
+
+ rc = nfs_multipath_parse_options_check_valid(options);
+
+ if (rc != 0) {
+ pr_err("has invaild ip.\n");
+ return rc;
+ }
+
+ rc = nfs_multipath_parse_options_check_duplicate(options);
+ if (rc != 0)
+ return rc;
+ return rc;
+}
+
+int nfs_multipath_alloc_options(void **enfs_option)
+{
+ struct multipath_mount_options *options = NULL;
+
+ options = kzalloc(sizeof(struct multipath_mount_options), GFP_KERNEL);
+
+ if (options == NULL)
+ return -ENOMEM;
+
+ options->local_ip_list =
+ kzalloc(sizeof(struct nfs_ip_list), GFP_KERNEL);
+ if (options->local_ip_list == NULL) {
+ kfree(options);
+ return -ENOMEM;
+ }
+
+ options->remote_ip_list =
+ kzalloc(sizeof(struct nfs_ip_list), GFP_KERNEL);
+ if (options->remote_ip_list == NULL) {
+ kfree(options->local_ip_list);
+ kfree(options);
+ return -ENOMEM;
+ }
+
+ options->pRemoteDnsInfo = kzalloc(sizeof(struct NFS_ROUTE_DNS_INFO_S),
+ GFP_KERNEL);
+ if (options->pRemoteDnsInfo == NULL) {
+ kfree(options->remote_ip_list);
+ kfree(options->local_ip_list);
+ kfree(options);
+ return -ENOMEM;
+ }
+
+ *enfs_option = options;
+ return 0;
+}
+
+int nfs_multipath_parse_options(enum nfs_multi_path_options type,
+ char *str, void **enfs_option, struct net *net_ns)
+{
+ int rc;
+ struct multipath_mount_options *options = NULL;
+
+ if ((str == NULL) || (enfs_option == NULL) || (net_ns == NULL))
+ return -EINVAL;
+
+ if (*enfs_option == NULL) {
+ rc = nfs_multipath_alloc_options(enfs_option);
+ if (rc != 0) {
+ enfs_log_error(
+ "alloc enfs_options failed! errno:%d\n", rc);
+ return rc;
+ }
+ }
+
+ options = (struct multipath_mount_options *)*enfs_option;
+
+ if (type == LOCALADDR || type == REMOUNTLOCALADDR ||
+ type == REMOTEADDR || type == REMOUNTREMOTEADDR) {
+ rc = nfs_multipath_parse_ip_list(str, net_ns, options, type);
+ } else if (type == REMOTEDNSNAME) {
+ /* alloc and release need to modify */
+ rc = nfs_multipath_parse_dns_list(str, net_ns, options);
+ } else {
+ rc = -EOPNOTSUPP;
+ }
+
+ // after parsing cmd, need checking local and remote
+ // IP is same. if not means illegal cmd
+ if (rc == 0)
+ rc = nfs_multipath_parse_options_check_duplicate(options);
+
+ if (rc == 0)
+ rc = nfs_multipath_parse_options_check(options);
+
+ return rc;
+}
+
+void nfs_multipath_free_options(void **enfs_option)
+{
+ struct multipath_mount_options *options;
+
+ if (enfs_option == NULL || *enfs_option == NULL)
+ return;
+
+ options = (struct multipath_mount_options *)*enfs_option;
+
+ if (options->remote_ip_list != NULL) {
+ kfree(options->remote_ip_list);
+ options->remote_ip_list = NULL;
+ }
+
+ if (options->local_ip_list != NULL) {
+ kfree(options->local_ip_list);
+ options->local_ip_list = NULL;
+ }
+
+ if (options->pRemoteDnsInfo != NULL) {
+ kfree(options->pRemoteDnsInfo);
+ options->pRemoteDnsInfo = NULL;
+ }
+
+ kfree(options);
+ *enfs_option = NULL;
+}
diff --git a/fs/nfs/enfs/enfs_multipath_parse.h b/fs/nfs/enfs/enfs_multipath_parse.h
new file mode 100644
index 000000000000..d5a36b2eb63b
--- /dev/null
+++ b/fs/nfs/enfs/enfs_multipath_parse.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Client-side ENFS adapter.
+ *
+ * Copyright (c) 2023. Huawei Technologies Co., Ltd. All rights reserved.
+ */
+#ifndef _ENFS_MULTIPATH_PARSE_H_
+#define _ENFS_MULTIPATH_PARSE_H_
+
+#include "enfs.h"
+
+struct multipath_mount_options {
+ struct nfs_ip_list *remote_ip_list;
+ struct nfs_ip_list *local_ip_list;
+ struct NFS_ROUTE_DNS_INFO_S *pRemoteDnsInfo;
+};
+
+int nfs_multipath_parse_options(enum nfs_multi_path_options type,
+ char *str, void **enfs_option, struct net *net_ns);
+void nfs_multipath_free_options(void **enfs_option);
+
+#endif