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 +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#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