| /* |
| ************************************************************************** |
| * Copyright (c) 2015-2016,2018-2020, The Linux Foundation. All rights reserved. |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all copies. |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| ************************************************************************** |
| */ |
| |
| /* |
| * nss_nlgre_redir.c |
| * NSS Netlink gre_redir Handler |
| */ |
| #include <linux/version.h> |
| #include <net/genetlink.h> |
| #include <nss_api_if.h> |
| #include <nss_nlcmn_if.h> |
| #include <nss_nl_if.h> |
| #include <nss_nlgre_redir_if.h> |
| #include "nss_nl.h" |
| #include "nss_nlgre_redir.h" |
| #include "nss_nlgre_redir_cmd.h" |
| #include "nss_nlgre_redir_cmn.h" |
| #include "nss_nlgre_redir_lag.h" |
| #include "nss_nlipv6_if.h" |
| #include "nss_nlipv4_if.h" |
| |
| /* |
| * To get lock on deploy_mode |
| */ |
| static DEFINE_SPINLOCK(lock); |
| |
| /* |
| * Variable to keep track of mode we are operating |
| */ |
| static enum nss_nlgre_redir_cmd_deploy_mode deploy_mode; |
| |
| /* |
| * prototypes |
| */ |
| static int nss_nlgre_redir_cmd_ops_tun_create(struct sk_buff *skb, struct genl_info *info); |
| static int nss_nlgre_redir_cmd_ops_tun_destroy(struct sk_buff *skb, struct genl_info *info); |
| static int nss_nlgre_redir_cmd_ops_map(struct sk_buff *skb, struct genl_info *info); |
| static int nss_nlgre_redir_cmd_ops_unmap(struct sk_buff *skb, struct genl_info *info); |
| static int nss_nlgre_redir_cmd_ops_set_next(struct sk_buff *skb, struct genl_info *info); |
| static int nss_nlgre_redir_cmd_ops_add_hash(struct sk_buff *skb, struct genl_info *info); |
| static int nss_nlgre_redir_cmd_ops_del_hash(struct sk_buff *skb, struct genl_info *info); |
| |
| /* |
| * nss_nlgre_redir_cmd_mcgrp |
| * Multicast group for sending message status & events |
| */ |
| static const struct genl_multicast_group nss_nlgre_redir_family_mcgrp[] = { |
| {.name = NSS_NLGRE_REDIR_MCAST_GRP}, |
| }; |
| |
| /* |
| * nss_nlgre_redir_ops |
| * Operation table called by the generic netlink layer based on the command |
| */ |
| static struct genl_ops nss_nlgre_redir_ops[] = { |
| {.cmd = NSS_NLGRE_REDIR_CMD_TYPE_CREATE_TUN, .doit = nss_nlgre_redir_cmd_ops_tun_create,}, |
| {.cmd = NSS_NLGRE_REDIR_CMD_TYPE_DESTROY_TUN, .doit = nss_nlgre_redir_cmd_ops_tun_destroy,}, |
| {.cmd = NSS_NLGRE_REDIR_CMD_TYPE_MAP, .doit = nss_nlgre_redir_cmd_ops_map,}, |
| {.cmd = NSS_NLGRE_REDIR_CMD_TYPE_UNMAP, .doit = nss_nlgre_redir_cmd_ops_unmap,}, |
| {.cmd = NSS_NLGRE_REDIR_CMD_TYPE_SET_NEXT_HOP, .doit = nss_nlgre_redir_cmd_ops_set_next,}, |
| {.cmd = NSS_NLGRE_REDIR_CMD_TYPE_ADD_HASH, .doit = nss_nlgre_redir_cmd_ops_add_hash,}, |
| {.cmd = NSS_NLGRE_REDIR_CMD_TYPE_DEL_HASH, .doit = nss_nlgre_redir_cmd_ops_del_hash,}, |
| }; |
| |
| /* |
| * nss_nlgre_redir_cmd_family |
| * Gre_redir family definition |
| */ |
| struct genl_family nss_nlgre_redir_cmd_family = { |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 0)) |
| .id = GENL_ID_GENERATE, /* Auto generate ID */ |
| #endif |
| .name = NSS_NLGRE_REDIR_FAMILY, /* family name string */ |
| .hdrsize = sizeof(struct nss_nlgre_redir_rule), /* NSS NETLINK gre_redir rule */ |
| .version = NSS_NL_VER, /* Set it to NSS_NLGRE_REDIR version */ |
| .maxattr = NSS_NLGRE_REDIR_CMD_TYPE_MAX, /* maximum commands supported */ |
| .netnsok = true, |
| .pre_doit = NULL, |
| .post_doit = NULL, |
| .ops = nss_nlgre_redir_ops, |
| .n_ops = ARRAY_SIZE(nss_nlgre_redir_ops), |
| .mcgrps = nss_nlgre_redir_family_mcgrp, |
| .n_mcgrps = ARRAY_SIZE(nss_nlgre_redir_family_mcgrp) |
| }; |
| |
| /* |
| * nss_nlgre_redir_cmd_get_deploy_mode() |
| * Returns deploy_mode value |
| */ |
| static inline enum nss_nlgre_redir_cmd_deploy_mode nss_nlgre_redir_cmd_get_deploy_mode(void) |
| { |
| enum nss_nlgre_redir_cmd_deploy_mode ret_deploy_mode; |
| spin_lock(&lock); |
| ret_deploy_mode = deploy_mode; |
| spin_unlock(&lock); |
| return ret_deploy_mode; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_set_deploy_mode() |
| * Sets the value of deploy_mode to parameter passed |
| */ |
| static inline void nss_nlgre_redir_cmd_set_deploy_mode(enum nss_nlgre_redir_cmd_deploy_mode param_deploy_mode) |
| { |
| spin_lock(&lock); |
| deploy_mode = param_deploy_mode; |
| spin_unlock(&lock); |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_ops_tun_create() |
| * Handler for tunnel create |
| */ |
| static int nss_nlgre_redir_cmd_ops_tun_create(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct nss_nlgre_redir_rule *nl_rule; |
| struct nss_nlcmn *nl_cm; |
| int ret = 0; |
| |
| /* |
| * Extract the message payload |
| */ |
| nl_cm = nss_nl_get_msg(&nss_nlgre_redir_cmd_family, info, NSS_NLGRE_REDIR_CMD_TYPE_CREATE_TUN); |
| if (!nl_cm) { |
| nss_nl_error("Unable to extract create tunnel data\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Message validation required before accepting the configuration |
| */ |
| nl_rule = container_of(nl_cm, struct nss_nlgre_redir_rule, cm); |
| |
| /* |
| * Create tunnel based on value of lag_enable |
| */ |
| if (!nl_rule->msg.create.lag_enable) { |
| nss_nlgre_redir_cmd_set_deploy_mode(NSS_NLGRE_REDIR_CMD_DEPLOY_MODE_NON_LAG); |
| ret = nss_nlgre_redir_create_tun(&nl_rule->msg.create); |
| if (ret < 0) { |
| nss_nl_error("Unable to create tunnel\n"); |
| return -EAGAIN; |
| } |
| |
| goto done; |
| } |
| |
| /* |
| * Create a lag tunnel |
| */ |
| nss_nlgre_redir_cmd_set_deploy_mode(NSS_NLGRE_REDIR_CMD_DEPLOY_MODE_LAG); |
| ret = nss_nlgre_redir_lag_create_tun(&nl_rule->msg.create); |
| if (ret < 0) { |
| nss_nl_error("Unable to create lag tunnel\n"); |
| return -EAGAIN; |
| } |
| done: |
| nss_nl_info("Successfully created tunnel\n"); |
| return 0; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_ops_tun_destroy() |
| * Handler to destroy tunnel |
| */ |
| static int nss_nlgre_redir_cmd_ops_tun_destroy(struct sk_buff *skb, struct genl_info *info) |
| { |
| enum nss_nlgre_redir_cmd_deploy_mode deploy_mode; |
| struct nss_nlgre_redir_rule *nl_rule; |
| struct nss_nlcmn *nl_cm; |
| struct net_device *dev; |
| int ret = 0; |
| |
| /* |
| * Extract the message payload |
| */ |
| nl_cm = nss_nl_get_msg(&nss_nlgre_redir_cmd_family, info, NSS_NLGRE_REDIR_CMD_TYPE_DESTROY_TUN); |
| if (!nl_cm) { |
| nss_nl_error("Unable to extract destroy tunnel data\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Message validation required before accepting the configuration |
| */ |
| nl_rule = container_of(nl_cm, struct nss_nlgre_redir_rule, cm); |
| |
| /* |
| * Get the dev reference |
| */ |
| dev = dev_get_by_name(&init_net, nl_rule->msg.destroy.netdev); |
| if (!dev) { |
| nss_nl_error("Invalid parameters: %s\n", nl_rule->msg.destroy.netdev); |
| return -ENODEV; |
| } |
| |
| dev_put(dev); |
| |
| /* |
| * Destroy the non-lag tunnel |
| */ |
| deploy_mode = nss_nlgre_redir_cmd_get_deploy_mode(); |
| if (deploy_mode != NSS_NLGRE_REDIR_CMD_DEPLOY_MODE_LAG) { |
| ret = nss_nlgre_redir_destroy_tun(dev); |
| if (ret < 0) { |
| nss_nl_error("Unable to destroy tunnel: %s\n", nl_rule->msg.destroy.netdev); |
| dev_put(dev); |
| return -EAGAIN; |
| } |
| |
| goto done; |
| } |
| |
| /* |
| * Destroy the lag tunnel |
| */ |
| ret = nss_nlgre_redir_lag_destroy_tun(dev); |
| if (ret < 0) { |
| nss_nl_error("Unable to destroy tunnel: %s\n", nl_rule->msg.destroy.netdev); |
| dev_put(dev); |
| return -EAGAIN; |
| } |
| |
| done: |
| nss_nl_info("Successfully destroyed gretun = %s tunnel\n", nl_rule->msg.destroy.netdev); |
| return 0; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_ops_map() |
| * Handler for map command |
| */ |
| static int nss_nlgre_redir_cmd_ops_map(struct sk_buff *skb, struct genl_info *info) |
| { |
| enum nss_nlgre_redir_cmd_deploy_mode deploy_mode; |
| struct nss_nlgre_redir_rule *nl_rule; |
| struct nss_nlcmn *nl_cm; |
| int ret = 0; |
| |
| /* |
| * extract the message payload |
| */ |
| nl_cm = nss_nl_get_msg(&nss_nlgre_redir_cmd_family, info, NSS_NLGRE_REDIR_CMD_TYPE_MAP); |
| if (!nl_cm) { |
| nss_nl_error("Unable to extract map interface data\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Message validation required before accepting the configuration |
| */ |
| nl_rule = container_of(nl_cm, struct nss_nlgre_redir_rule, cm); |
| |
| /* |
| * Map the interface |
| */ |
| deploy_mode = nss_nlgre_redir_cmd_get_deploy_mode(); |
| if (deploy_mode != NSS_NLGRE_REDIR_CMD_DEPLOY_MODE_LAG) { |
| ret = nss_nlgre_redir_map_interface(&nl_rule->msg.map); |
| if(ret < 0) { |
| nss_nl_error("Unable to map nss interface\n"); |
| return -EAGAIN; |
| } |
| |
| goto done; |
| } |
| |
| ret = nss_nlgre_redir_lag_map_interface(&nl_rule->msg.map); |
| if (ret < 0) { |
| nss_nl_error("Unable to map nss interface\n"); |
| return -EAGAIN; |
| } |
| done: |
| nss_nl_info("Successfully mapped nss interface.\n"); |
| return 0; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_ops_unmap() |
| * Handler for unmap command |
| */ |
| static int nss_nlgre_redir_cmd_ops_unmap(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct nss_nlgre_redir_rule *nl_rule; |
| struct nss_nlcmn *nl_cm; |
| int ret = 0; |
| |
| /* |
| * extract the message payload |
| */ |
| nl_cm = nss_nl_get_msg(&nss_nlgre_redir_cmd_family, info, NSS_NLGRE_REDIR_CMD_TYPE_UNMAP); |
| if (!nl_cm) { |
| nss_nl_error("Unable to extract unmap data\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Message validation required before accepting the configuration |
| */ |
| nl_rule = container_of(nl_cm, struct nss_nlgre_redir_rule, cm); |
| |
| /* |
| * Unmap the interface |
| */ |
| ret = nss_nlgre_redir_cmn_unmap_interface(&nl_rule->msg.unmap); |
| if(ret < 0) { |
| nss_nl_error("Unable to unmap nss interface\n"); |
| return -EAGAIN; |
| } |
| |
| nss_nl_info("Successfully unmapped the nss interface.\n"); |
| return 0; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_ops_set_next() |
| * Handler for set_next command |
| */ |
| static int nss_nlgre_redir_cmd_ops_set_next(struct sk_buff *skb, struct genl_info *info) |
| { |
| enum nss_nlgre_redir_cmd_deploy_mode deploy_mode; |
| struct nss_nlgre_redir_rule *nl_rule; |
| struct nss_nlcmn *nl_cm; |
| int ret = 0; |
| |
| /* |
| * extract the message payload |
| */ |
| nl_cm = nss_nl_get_msg(&nss_nlgre_redir_cmd_family, info, NSS_NLGRE_REDIR_CMD_TYPE_SET_NEXT_HOP); |
| if (!nl_cm) { |
| nss_nl_error("Unable to extract set_next_hop data\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Message validation required before accepting the configuration |
| */ |
| nl_rule = container_of(nl_cm, struct nss_nlgre_redir_rule, cm); |
| |
| /* |
| * Set the next hop of ath0 as wifi_offld_inner of gre_redir node |
| */ |
| deploy_mode = nss_nlgre_redir_cmd_get_deploy_mode(); |
| if (deploy_mode != NSS_NLGRE_REDIR_CMD_DEPLOY_MODE_LAG) { |
| ret = nss_nlgre_redir_set_next_hop(&nl_rule->msg.snext); |
| if (ret < 0) { |
| nss_nl_error("Unable to set next hop\n"); |
| return -EAGAIN; |
| } |
| |
| goto done; |
| } |
| |
| /* |
| * Set the next hop of ath0 as lag US node's inner interface |
| */ |
| ret = nss_nlgre_redir_lag_set_next_hop(&nl_rule->msg.snext); |
| if (ret < 0) { |
| nss_nl_error("Unable to set the next hop\n"); |
| return -EAGAIN; |
| } |
| |
| done: |
| nss_nl_info("Successfully set the next hop\n"); |
| return 0; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_ops_add_hash() |
| * Handler for adding hash a value |
| */ |
| static int nss_nlgre_redir_cmd_ops_add_hash(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct nss_nlgre_redir_rule *nl_rule; |
| struct nss_nlcmn *nl_cm; |
| int ret; |
| |
| /* |
| * extract the message payload |
| */ |
| nl_cm = nss_nl_get_msg(&nss_nlgre_redir_cmd_family, info, NSS_NLGRE_REDIR_CMD_TYPE_ADD_HASH); |
| if (!nl_cm) { |
| nss_nl_error("Unable to add a new hash value.\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Message validation required before accepting the configuration |
| */ |
| nl_rule = container_of(nl_cm, struct nss_nlgre_redir_rule, cm); |
| ret = nss_nlgre_redir_lag_add_hash(&nl_rule->msg.hash_ops); |
| if(ret < 0) { |
| nss_nl_error("Unable to add hash value.\n"); |
| return -EINVAL; |
| } |
| |
| nss_nl_info("Successfully added a hash value.\n"); |
| return 0; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_ops_del_hash() |
| * Handler for deleting a hash value |
| */ |
| static int nss_nlgre_redir_cmd_ops_del_hash(struct sk_buff *skb, struct genl_info *info) |
| { |
| struct nss_nlgre_redir_rule *nl_rule; |
| struct nss_nlcmn *nl_cm; |
| int ret; |
| |
| /* |
| * extract the message payload |
| */ |
| nl_cm = nss_nl_get_msg(&nss_nlgre_redir_cmd_family, info, NSS_NLGRE_REDIR_CMD_TYPE_DEL_HASH); |
| if (!nl_cm) { |
| nss_nl_error("Unable to delete the hash value.\n"); |
| return -EINVAL; |
| } |
| |
| /* |
| * Message validation required before accepting the configuration |
| */ |
| nl_rule = container_of(nl_cm, struct nss_nlgre_redir_rule, cm); |
| |
| /* |
| * Delete hash value corresponding to smac and dmac |
| */ |
| ret = nss_nlgre_redir_lag_del_hash(&nl_rule->msg.hash_ops); |
| if(ret < 0) { |
| nss_nl_error("Unable to delete hash value.\n"); |
| return -EINVAL; |
| } |
| |
| nss_nl_info("Successfully deleted the hash entry.\n"); |
| return 0; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_get_ifnum() |
| * Get the interface number corresponding to netdev |
| */ |
| int nss_nlgre_redir_cmd_get_ifnum(struct net_device *dev, uint8_t proto) |
| { |
| enum nss_dynamic_interface_type type; |
| int ifnum; |
| |
| switch (proto) { |
| case IPPROTO_TCP: |
| case IPPROTO_UDP: |
| case IPPROTO_UDPLITE: |
| type = NSS_DYNAMIC_INTERFACE_TYPE_GRE_REDIR_WIFI_OFFL_INNER; |
| break; |
| case IPPROTO_GRE: |
| type = NSS_DYNAMIC_INTERFACE_TYPE_GRE_REDIR_OUTER; |
| break; |
| default: |
| nss_nl_error("Invalid protocol %d\n", proto); |
| return -1; |
| } |
| |
| /* |
| * Get the interface number depending upon the dev and type |
| */ |
| ifnum = nss_cmn_get_interface_number_by_dev_and_type(dev, type); |
| if (ifnum < 0) { |
| nss_nl_error("%px: Failed to find interface number (dev:%s, type:%d)\n", dev, dev->name, type); |
| return -1; |
| } |
| |
| return ifnum; |
| } |
| |
| /* |
| * nss_nlgre_redir_cmd_get_mtu() |
| * Returns the mtu based on the device passed |
| */ |
| int nss_nlgre_redir_cmd_get_mtu(struct net_device *dev, uint8_t iptype, int ifnum) |
| { |
| enum nss_dynamic_interface_type type; |
| struct nss_ctx_instance *nss_ctx; |
| int mtu = dev->mtu; |
| |
| nss_ctx = nss_gre_redir_get_context(); |
| type = nss_dynamic_interface_get_type(nss_ctx, ifnum); |
| switch (iptype) { |
| case NSS_GRE_REDIR_IP_HDR_TYPE_IPV4: |
| if (type == NSS_DYNAMIC_INTERFACE_TYPE_GRE_REDIR_OUTER) { |
| mtu = NSS_NLIPV4_MAX_MTU; |
| } else if (mtu < NSS_NLIPV4_MIN_MTU) { |
| mtu = NSS_NLIPV4_MIN_MTU; |
| } |
| |
| break; |
| case NSS_GRE_REDIR_IP_HDR_TYPE_IPV6: |
| if (type == NSS_DYNAMIC_INTERFACE_TYPE_GRE_REDIR_OUTER) { |
| mtu = NSS_NLIPV6_MAX_MTU; |
| } else if (mtu < NSS_NLIPV6_MIN_MTU) { |
| mtu = NSS_NLIPV6_MIN_MTU; |
| } |
| |
| break; |
| } |
| |
| return mtu; |
| } |